mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-05-22 13:38:39 +03:00
Merge branch 'rustdesk:master' into master
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,10 @@ impl Interface for Session {
|
||||
}
|
||||
|
||||
async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream) {
|
||||
log::info!(
|
||||
"password={}",
|
||||
hbb_common::password_security::temporary_password()
|
||||
);
|
||||
handle_hash(self.lc.clone(), &pass, hash, self, peer).await;
|
||||
}
|
||||
|
||||
|
||||
292
src/client.rs
292
src/client.rs
@@ -1,4 +1,13 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::SocketAddr,
|
||||
ops::{Deref, Not},
|
||||
str::FromStr,
|
||||
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::{
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
@@ -6,15 +15,11 @@ use cpal::{
|
||||
};
|
||||
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::SocketAddr,
|
||||
ops::{Deref, Not},
|
||||
sync::{atomic::AtomicBool, mpsc, Arc, Mutex, RwLock},
|
||||
};
|
||||
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::{
|
||||
allow_err,
|
||||
anyhow::{anyhow, Context},
|
||||
@@ -23,7 +28,7 @@ use hbb_common::{
|
||||
Config, PeerConfig, PeerInfoSerde, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT,
|
||||
RENDEZVOUS_TIMEOUT,
|
||||
},
|
||||
log,
|
||||
get_version_number, log,
|
||||
message_proto::{option_message::BoolOption, *},
|
||||
protobuf::Message as _,
|
||||
rand,
|
||||
@@ -40,6 +45,18 @@ use scrap::{
|
||||
codec::{Decoder, DecoderCfg},
|
||||
record::{Recorder, RecorderContext},
|
||||
VpxDecoderConfig, VpxVideoCodecId,
|
||||
ImageFormat,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
common::{self, is_keyboard_mode_supported},
|
||||
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::*;
|
||||
@@ -47,16 +64,19 @@ pub use super::lang::*;
|
||||
pub mod file_trait;
|
||||
pub mod helper;
|
||||
pub mod io_loop;
|
||||
use crate::server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED};
|
||||
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();
|
||||
@@ -65,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")))]
|
||||
@@ -80,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;
|
||||
@@ -177,6 +199,13 @@ impl Client {
|
||||
true,
|
||||
));
|
||||
}
|
||||
// Allow connect to {domain}:{port}
|
||||
if hbb_common::is_domain_port_str(peer) {
|
||||
return Ok((
|
||||
socket_client::connect_tcp(peer, RENDEZVOUS_TIMEOUT).await?,
|
||||
true,
|
||||
));
|
||||
}
|
||||
let (mut rendezvous_server, servers, contained) = crate::get_rendezvous_server(1_000).await;
|
||||
let mut socket = socket_client::connect_tcp(&*rendezvous_server, RENDEZVOUS_TIMEOUT).await;
|
||||
debug_assert!(!servers.contains(&rendezvous_server));
|
||||
@@ -583,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`].
|
||||
@@ -702,6 +811,7 @@ impl AudioHandler {
|
||||
.check_audio(frame.timestamp)
|
||||
.not()
|
||||
{
|
||||
log::debug!("audio frame {} is ignored", frame.timestamp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -712,6 +822,7 @@ impl AudioHandler {
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
if self.simple.is_none() {
|
||||
log::debug!("PulseAudio simple binding does not exists");
|
||||
return;
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
@@ -825,7 +936,7 @@ impl VideoHandler {
|
||||
/// Handle a new video frame.
|
||||
pub fn handle_frame(&mut self, vf: VideoFrame) -> ResultType<bool> {
|
||||
if vf.timestamp != 0 {
|
||||
// Update the lantency controller with the latest timestamp.
|
||||
// Update the latency controller with the latest timestamp.
|
||||
self.latency_controller
|
||||
.lock()
|
||||
.unwrap()
|
||||
@@ -833,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()
|
||||
@@ -898,6 +1014,9 @@ pub struct LoginConfigHandler {
|
||||
pub force_relay: bool,
|
||||
pub direct: Option<bool>,
|
||||
pub received: bool,
|
||||
switch_uuid: Option<String>,
|
||||
pub success_time: Option<hbb_common::tokio::time::Instant>,
|
||||
pub direct_error_counter: usize,
|
||||
}
|
||||
|
||||
impl Deref for LoginConfigHandler {
|
||||
@@ -925,7 +1044,13 @@ impl LoginConfigHandler {
|
||||
///
|
||||
/// * `id` - id of peer
|
||||
/// * `conn_type` - Connection type enum.
|
||||
pub fn initialize(&mut self, id: String, conn_type: ConnType) {
|
||||
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();
|
||||
@@ -934,15 +1059,18 @@ 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.
|
||||
/// Return password if the client should auto login, otherwise return empty string.
|
||||
pub fn should_auto_login(&self) -> String {
|
||||
let l = self.lock_after_session_end;
|
||||
let l = self.lock_after_session_end.v;
|
||||
let a = !self.get_option("auto-login").is_empty();
|
||||
let p = self.get_option("os-password");
|
||||
if !p.is_empty() && l && a {
|
||||
@@ -1049,32 +1177,32 @@ impl LoginConfigHandler {
|
||||
let mut option = OptionMessage::default();
|
||||
let mut config = self.load_config();
|
||||
if name == "show-remote-cursor" {
|
||||
config.show_remote_cursor = !config.show_remote_cursor;
|
||||
option.show_remote_cursor = (if config.show_remote_cursor {
|
||||
config.show_remote_cursor.v = !config.show_remote_cursor.v;
|
||||
option.show_remote_cursor = (if config.show_remote_cursor.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
})
|
||||
.into();
|
||||
} else if name == "disable-audio" {
|
||||
config.disable_audio = !config.disable_audio;
|
||||
option.disable_audio = (if config.disable_audio {
|
||||
config.disable_audio.v = !config.disable_audio.v;
|
||||
option.disable_audio = (if config.disable_audio.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
})
|
||||
.into();
|
||||
} else if name == "disable-clipboard" {
|
||||
config.disable_clipboard = !config.disable_clipboard;
|
||||
option.disable_clipboard = (if config.disable_clipboard {
|
||||
config.disable_clipboard.v = !config.disable_clipboard.v;
|
||||
option.disable_clipboard = (if config.disable_clipboard.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
})
|
||||
.into();
|
||||
} else if name == "lock-after-session-end" {
|
||||
config.lock_after_session_end = !config.lock_after_session_end;
|
||||
option.lock_after_session_end = (if config.lock_after_session_end {
|
||||
config.lock_after_session_end.v = !config.lock_after_session_end.v;
|
||||
option.lock_after_session_end = (if config.lock_after_session_end.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
@@ -1082,15 +1210,15 @@ impl LoginConfigHandler {
|
||||
.into();
|
||||
} else if name == "privacy-mode" {
|
||||
// try toggle privacy mode
|
||||
option.privacy_mode = (if config.privacy_mode {
|
||||
option.privacy_mode = (if config.privacy_mode.v {
|
||||
BoolOption::No
|
||||
} else {
|
||||
BoolOption::Yes
|
||||
})
|
||||
.into();
|
||||
} else if name == "enable-file-transfer" {
|
||||
config.enable_file_transfer = !config.enable_file_transfer;
|
||||
option.enable_file_transfer = (if config.enable_file_transfer {
|
||||
config.enable_file_transfer.v = !config.enable_file_transfer.v;
|
||||
option.enable_file_transfer = (if config.enable_file_transfer.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
@@ -1101,10 +1229,16 @@ impl LoginConfigHandler {
|
||||
} else if name == "unblock-input" {
|
||||
option.block_input = BoolOption::No.into();
|
||||
} else if name == "show-quality-monitor" {
|
||||
config.show_quality_monitor = !config.show_quality_monitor;
|
||||
config.show_quality_monitor.v = !config.show_quality_monitor.v;
|
||||
} else if name == "allow_swap_key" {
|
||||
config.allow_swap_key.v = !config.allow_swap_key.v;
|
||||
} else {
|
||||
let v = self.options.get(&name).is_some();
|
||||
if v {
|
||||
let is_set = self
|
||||
.options
|
||||
.get(&name)
|
||||
.map(|o| !o.is_empty())
|
||||
.unwrap_or(false);
|
||||
if is_set {
|
||||
self.config.options.remove(&name);
|
||||
} else {
|
||||
self.config.options.insert(name, "Y".to_owned());
|
||||
@@ -1238,19 +1372,21 @@ impl LoginConfigHandler {
|
||||
/// * `name` - The name of the toggle option.
|
||||
pub fn get_toggle_option(&self, name: &str) -> bool {
|
||||
if name == "show-remote-cursor" {
|
||||
self.config.show_remote_cursor
|
||||
self.config.show_remote_cursor.v
|
||||
} else if name == "lock-after-session-end" {
|
||||
self.config.lock_after_session_end
|
||||
self.config.lock_after_session_end.v
|
||||
} else if name == "privacy-mode" {
|
||||
self.config.privacy_mode
|
||||
self.config.privacy_mode.v
|
||||
} else if name == "enable-file-transfer" {
|
||||
self.config.enable_file_transfer
|
||||
self.config.enable_file_transfer.v
|
||||
} else if name == "disable-audio" {
|
||||
self.config.disable_audio
|
||||
self.config.disable_audio.v
|
||||
} else if name == "disable-clipboard" {
|
||||
self.config.disable_clipboard
|
||||
self.config.disable_clipboard.v
|
||||
} else if name == "show-quality-monitor" {
|
||||
self.config.show_quality_monitor
|
||||
self.config.show_quality_monitor.v
|
||||
} else if name == "allow_swap_key" {
|
||||
self.config.allow_swap_key.v
|
||||
} else {
|
||||
!self.get_option(name).is_empty()
|
||||
}
|
||||
@@ -1392,12 +1528,18 @@ impl LoginConfigHandler {
|
||||
log::debug!("remove password of {}", self.id);
|
||||
}
|
||||
}
|
||||
if config.keyboard_mode == "" {
|
||||
if hbb_common::get_version_number(&pi.version) < hbb_common::get_version_number("1.2.0")
|
||||
{
|
||||
config.keyboard_mode = "legacy".to_string();
|
||||
if config.keyboard_mode.is_empty() {
|
||||
if is_keyboard_mode_supported(&KeyboardMode::Map, get_version_number(&pi.version)) {
|
||||
config.keyboard_mode = KeyboardMode::Map.to_string();
|
||||
} else {
|
||||
config.keyboard_mode = "map".to_string();
|
||||
config.keyboard_mode = KeyboardMode::Legacy.to_string();
|
||||
}
|
||||
} else {
|
||||
let keyboard_modes =
|
||||
common::get_supported_keyboard_modes(get_version_number(&pi.version));
|
||||
let current_mode = &KeyboardMode::from_str(&config.keyboard_mode).unwrap_or_default();
|
||||
if !keyboard_modes.contains(current_mode) {
|
||||
config.keyboard_mode = KeyboardMode::Legacy.to_string();
|
||||
}
|
||||
}
|
||||
self.conn_id = pi.conn_id;
|
||||
@@ -1516,10 +1658,9 @@ 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 (audio_sender, audio_receiver) = mpsc::channel::<MediaData>();
|
||||
let mut video_callback = video_callback;
|
||||
|
||||
let latency_controller = LatencyController::new();
|
||||
@@ -1532,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 => {
|
||||
@@ -1549,8 +1690,19 @@ where
|
||||
}
|
||||
log::info!("Video decoder loop exits");
|
||||
});
|
||||
let audio_sender = start_audio_thread(Some(latency_controller_cl));
|
||||
return (video_sender, audio_sender);
|
||||
}
|
||||
|
||||
/// Start an audio thread
|
||||
/// Return a audio [`MediaSender`]
|
||||
pub fn start_audio_thread(
|
||||
latency_controller: Option<Arc<Mutex<LatencyController>>>,
|
||||
) -> MediaSender {
|
||||
let latency_controller = latency_controller.unwrap_or(LatencyController::new());
|
||||
let (audio_sender, audio_receiver) = mpsc::channel::<MediaData>();
|
||||
std::thread::spawn(move || {
|
||||
let mut audio_handler = AudioHandler::new(latency_controller_cl);
|
||||
let mut audio_handler = AudioHandler::new(latency_controller);
|
||||
loop {
|
||||
if let Ok(data) = audio_receiver.recv() {
|
||||
match data {
|
||||
@@ -1558,6 +1710,7 @@ where
|
||||
audio_handler.handle_frame(af);
|
||||
}
|
||||
MediaData::AudioFormat(f) => {
|
||||
log::debug!("recved audio format, sample rate={}", f.sample_rate);
|
||||
audio_handler.handle_format(f);
|
||||
}
|
||||
_ => {}
|
||||
@@ -1568,7 +1721,7 @@ where
|
||||
}
|
||||
log::info!("Audio decoder loop exits");
|
||||
});
|
||||
return (video_sender, audio_sender);
|
||||
audio_sender
|
||||
}
|
||||
|
||||
/// Handle latency test.
|
||||
@@ -1658,11 +1811,12 @@ pub fn send_mouse(
|
||||
if check_scroll_on_mac(mask, x, y) {
|
||||
mouse_event.modifiers.push(ControlKey::Scroll.into());
|
||||
}
|
||||
interface.swap_modifier_mouse(&mut mouse_event);
|
||||
msg_out.set_mouse_event(mouse_event);
|
||||
interface.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
/// Avtivate OS by sending mouse movement.
|
||||
/// Activate OS by sending mouse movement.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -1690,7 +1844,7 @@ fn activate_os(interface: &impl Interface) {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `p` - The password.
|
||||
/// * `avtivate` - Whether to activate OS.
|
||||
/// * `activate` - Whether to activate OS.
|
||||
/// * `interface` - The interface for sending data.
|
||||
pub fn input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
std::thread::spawn(move || {
|
||||
@@ -1703,7 +1857,7 @@ pub fn input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `p` - The password.
|
||||
/// * `avtivate` - Whether to activate OS.
|
||||
/// * `activate` - Whether to activate OS.
|
||||
/// * `interface` - The interface for sending data.
|
||||
fn _input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
if activate {
|
||||
@@ -1767,6 +1921,14 @@ pub async fn handle_hash(
|
||||
interface: &impl Interface,
|
||||
peer: &mut Stream,
|
||||
) {
|
||||
lc.write().unwrap().hash = hash.clone();
|
||||
let uuid = lc.read().unwrap().switch_uuid.clone();
|
||||
if let Some(uuid) = uuid {
|
||||
if let Ok(uuid) = uuid::Uuid::from_str(&uuid) {
|
||||
send_switch_login_request(lc.clone(), peer, uuid).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
let mut password = lc.read().unwrap().password.clone();
|
||||
if password.is_empty() {
|
||||
if !password_preset.is_empty() {
|
||||
@@ -1831,6 +1993,26 @@ pub async fn handle_login_from_ui(
|
||||
send_login(lc.clone(), hasher2.finalize()[..].into(), peer).await;
|
||||
}
|
||||
|
||||
async fn send_switch_login_request(
|
||||
lc: Arc<RwLock<LoginConfigHandler>>,
|
||||
peer: &mut Stream,
|
||||
uuid: Uuid,
|
||||
) {
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_switch_sides_response(SwitchSidesResponse {
|
||||
uuid: Bytes::from(uuid.as_bytes().to_vec()),
|
||||
lr: hbb_common::protobuf::MessageField::some(
|
||||
lc.read()
|
||||
.unwrap()
|
||||
.create_login_msg(vec![])
|
||||
.login_request()
|
||||
.to_owned(),
|
||||
),
|
||||
..Default::default()
|
||||
});
|
||||
allow_err!(peer.send(&msg_out).await);
|
||||
}
|
||||
|
||||
/// Interface for client to send data and commands.
|
||||
#[async_trait]
|
||||
pub trait Interface: Send + Clone + 'static + Sized {
|
||||
@@ -1856,6 +2038,7 @@ pub trait Interface: Send + Clone + 'static + Sized {
|
||||
fn is_force_relay(&self) -> bool {
|
||||
self.get_login_config_handler().read().unwrap().force_relay
|
||||
}
|
||||
fn swap_modifier_mouse(&self, _msg : &mut hbb_common::protos::message::MouseEvent) {}
|
||||
}
|
||||
|
||||
/// Data used by the client interface.
|
||||
@@ -1880,6 +2063,10 @@ pub enum Data {
|
||||
AddJob((i32, String, String, i32, bool, bool)),
|
||||
ResumeJob((i32, bool)),
|
||||
RecordScreen(bool, i32, i32, String),
|
||||
ElevateDirect,
|
||||
ElevateWithLogon(String, String),
|
||||
NewVoiceCall,
|
||||
CloseVoiceCall,
|
||||
}
|
||||
|
||||
/// Keycode for key events.
|
||||
@@ -2032,8 +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("reset by the peer")))
|
||||
&& !text.to_lowercase().contains("not allowed")))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
|
||||
use hbb_common::{
|
||||
log,
|
||||
message_proto::{video_frame, VideoFrame},
|
||||
message_proto::{video_frame, VideoFrame, Message, VoiceCallRequest, VoiceCallResponse}, get_time,
|
||||
};
|
||||
|
||||
const MAX_LATENCY: i64 = 500;
|
||||
@@ -15,9 +15,10 @@ const MIN_LATENCY: i64 = 100;
|
||||
/// Only sync the audio to video, not the other way around.
|
||||
#[derive(Debug)]
|
||||
pub struct LatencyController {
|
||||
last_video_remote_ts: i64, // generated on remote deivce
|
||||
last_video_remote_ts: i64, // generated on remote device
|
||||
update_time: Instant,
|
||||
allow_audio: bool,
|
||||
audio_only: bool
|
||||
}
|
||||
|
||||
impl Default for LatencyController {
|
||||
@@ -26,6 +27,7 @@ impl Default for LatencyController {
|
||||
last_video_remote_ts: Default::default(),
|
||||
update_time: Instant::now(),
|
||||
allow_audio: Default::default(),
|
||||
audio_only: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +38,11 @@ impl LatencyController {
|
||||
Arc::new(Mutex::new(LatencyController::default()))
|
||||
}
|
||||
|
||||
/// Set whether this [LatencyController] should be working in audio only mode.
|
||||
pub fn set_audio_only(&mut self, only: bool) {
|
||||
self.audio_only = only;
|
||||
}
|
||||
|
||||
/// Update the latency controller with the latest video timestamp.
|
||||
pub fn update_video(&mut self, timestamp: i64) {
|
||||
self.last_video_remote_ts = timestamp;
|
||||
@@ -46,7 +53,11 @@ impl LatencyController {
|
||||
pub fn check_audio(&mut self, timestamp: i64) -> bool {
|
||||
// Compute audio latency.
|
||||
let expected = self.update_time.elapsed().as_millis() as i64 + self.last_video_remote_ts;
|
||||
let latency = expected - timestamp;
|
||||
let latency = if self.audio_only {
|
||||
expected
|
||||
} else {
|
||||
expected - timestamp
|
||||
};
|
||||
// Set MAX and MIN, avoid fixing too frequently.
|
||||
if self.allow_audio {
|
||||
if latency.abs() > MAX_LATENCY {
|
||||
@@ -59,6 +70,9 @@ impl LatencyController {
|
||||
self.allow_audio = true;
|
||||
}
|
||||
}
|
||||
// No video frame here, which means the update time is not up to date.
|
||||
// We manually update the time here.
|
||||
self.update_time = Instant::now();
|
||||
self.allow_audio
|
||||
}
|
||||
}
|
||||
@@ -101,3 +115,24 @@ pub struct QualityStatus {
|
||||
pub target_bitrate: Option<i32>,
|
||||
pub codec_format: Option<CodecFormat>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_voice_call_request(is_connect: bool) -> Message {
|
||||
let mut req = VoiceCallRequest::new();
|
||||
req.is_connect = is_connect;
|
||||
req.req_timestamp = get_time();
|
||||
let mut msg = Message::new();
|
||||
msg.set_voice_call_request(req);
|
||||
msg
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_voice_call_response(request_timestamp: i64, accepted: bool) -> Message {
|
||||
let mut resp = VoiceCallResponse::new();
|
||||
resp.accepted = accepted;
|
||||
resp.req_timestamp = request_timestamp;
|
||||
resp.ack_timestamp = get_time();
|
||||
let mut msg = Message::new();
|
||||
msg.set_voice_call_response(resp);
|
||||
msg
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
use crate::client::{
|
||||
Client, CodecFormat, MediaData, MediaSender, QualityStatus, MILLI1, SEC30,
|
||||
SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED, SERVER_KEYBOARD_ENABLED,
|
||||
};
|
||||
use crate::common;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::common::{check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL};
|
||||
use std::collections::HashMap;
|
||||
use std::num::NonZeroI64;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(windows)]
|
||||
use clipboard::{cliprdr::CliprdrClientContext, ContextSend};
|
||||
|
||||
use crate::ui_session_interface::{InvokeUiSession, Session};
|
||||
use crate::{client::Data, client::Interface};
|
||||
|
||||
use hbb_common::config::{PeerConfig, TransferSerde};
|
||||
use hbb_common::fs::{
|
||||
can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult,
|
||||
@@ -20,6 +13,7 @@ use hbb_common::fs::{
|
||||
use hbb_common::message_proto::permission_info::Permission;
|
||||
use hbb_common::protobuf::Message as _;
|
||||
use hbb_common::rendezvous_proto::ConnType;
|
||||
use hbb_common::tokio::sync::mpsc::error::TryRecvError;
|
||||
#[cfg(windows)]
|
||||
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
||||
use hbb_common::tokio::{
|
||||
@@ -27,12 +21,19 @@ use hbb_common::tokio::{
|
||||
sync::mpsc,
|
||||
time::{self, Duration, Instant, Interval},
|
||||
};
|
||||
use hbb_common::{allow_err, message_proto::*, sleep};
|
||||
use hbb_common::{allow_err, get_time, message_proto::*, sleep};
|
||||
use hbb_common::{fs, log, Stream};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use crate::client::{
|
||||
new_voice_call_request, Client, CodecFormat, MediaData, MediaSender, QualityStatus, MILLI1,
|
||||
SEC30,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
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};
|
||||
use crate::{client::Data, client::Interface};
|
||||
|
||||
pub struct Remote<T: InvokeUiSession> {
|
||||
handler: Session<T>,
|
||||
@@ -40,6 +41,9 @@ pub struct Remote<T: InvokeUiSession> {
|
||||
audio_sender: MediaSender,
|
||||
receiver: mpsc::UnboundedReceiver<Data>,
|
||||
sender: mpsc::UnboundedSender<Data>,
|
||||
// Stop sending local audio to remote client.
|
||||
stop_voice_call_sender: Option<std::sync::mpsc::Sender<()>>,
|
||||
voice_call_request_timestamp: Option<NonZeroI64>,
|
||||
old_clipboard: Arc<Mutex<String>>,
|
||||
read_jobs: Vec<fs::TransferJob>,
|
||||
write_jobs: Vec<fs::TransferJob>,
|
||||
@@ -52,6 +56,7 @@ pub struct Remote<T: InvokeUiSession> {
|
||||
data_count: Arc<AtomicUsize>,
|
||||
frame_count: Arc<AtomicUsize>,
|
||||
video_format: CodecFormat,
|
||||
elevation_requested: bool,
|
||||
}
|
||||
|
||||
impl<T: InvokeUiSession> Remote<T> {
|
||||
@@ -81,11 +86,13 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
data_count: Arc::new(AtomicUsize::new(0)),
|
||||
frame_count,
|
||||
video_format: CodecFormat::Unknown,
|
||||
stop_voice_call_sender: None,
|
||||
voice_call_request_timestamp: None,
|
||||
elevation_requested: false,
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -93,6 +100,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
} else {
|
||||
ConnType::default()
|
||||
};
|
||||
|
||||
match Client::start(
|
||||
&self.handler.id,
|
||||
key,
|
||||
@@ -103,9 +111,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);
|
||||
|
||||
@@ -140,7 +145,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) => {
|
||||
@@ -212,18 +225,18 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
log::debug!("Exit io_loop of id={}", self.handler.id);
|
||||
// Stop client audio server.
|
||||
if let Some(s) = self.stop_voice_call_sender.take() {
|
||||
s.send(()).ok();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
self.handler
|
||||
.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>) {
|
||||
@@ -253,43 +266,78 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_clipboard(&mut self) -> Option<std::sync::mpsc::Sender<()>> {
|
||||
fn stop_voice_call(&mut self) {
|
||||
let voice_call_sender = std::mem::replace(&mut self.stop_voice_call_sender, None);
|
||||
if let Some(stopper) = voice_call_sender {
|
||||
let _ = stopper.send(());
|
||||
}
|
||||
}
|
||||
|
||||
// Start a voice call recorder, records audio and send to remote
|
||||
fn start_voice_call(&mut self) -> Option<std::sync::mpsc::Sender<()>> {
|
||||
if self.handler.is_file_transfer() || self.handler.is_port_forward() {
|
||||
return None;
|
||||
}
|
||||
// Switch to default input device
|
||||
let default_sound_device = get_default_sound_input();
|
||||
if let Some(device) = default_sound_device {
|
||||
set_sound_input(device);
|
||||
}
|
||||
// Create a channel to receive error or closed message
|
||||
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;
|
||||
let (tx_audio_data, mut rx_audio_data) = hbb_common::tokio::sync::mpsc::unbounded_channel();
|
||||
// Create a stand-alone inner, add subscribe to audio service
|
||||
let conn_id = CLIENT_SERVER.write().unwrap().get_new_id();
|
||||
let client_conn_inner = ConnInner::new(conn_id.clone(), Some(tx_audio_data), None);
|
||||
// now we subscribe
|
||||
CLIENT_SERVER.write().unwrap().subscribe(
|
||||
audio_service::NAME,
|
||||
client_conn_inner.clone(),
|
||||
true,
|
||||
);
|
||||
let tx_audio = self.sender.clone();
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
// check if client is closed
|
||||
match rx.try_recv() {
|
||||
Ok(_) | Err(std::sync::mpsc::TryRecvError::Disconnected) => {
|
||||
log::debug!("Exit voice call audio service of client");
|
||||
// unsubscribe
|
||||
CLIENT_SERVER.write().unwrap().subscribe(
|
||||
audio_service::NAME,
|
||||
client_conn_inner,
|
||||
false,
|
||||
);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match rx_audio_data.try_recv() {
|
||||
Ok((_instant, msg)) => match &msg.union {
|
||||
Some(message::Union::AudioFrame(frame)) => {
|
||||
let mut msg = Message::new();
|
||||
msg.set_audio_frame(frame.clone());
|
||||
tx_audio.send(Data::Message(msg)).ok();
|
||||
log::debug!("send audio frame {}", frame.timestamp);
|
||||
}
|
||||
Some(message::Union::Misc(misc)) => {
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc.clone());
|
||||
tx_audio.send(Data::Message(msg)).ok();
|
||||
log::debug!("send audio misc {:?}", misc.audio_format());
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(err) => {
|
||||
if err == TryRecvError::Empty {
|
||||
// ignore
|
||||
} else {
|
||||
log::debug!("Failed to record local audio channel: {}", err);
|
||||
}
|
||||
}
|
||||
if !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst)
|
||||
|| !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
|
||||
|| lc.read().unwrap().disable_clipboard
|
||||
{
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -632,6 +680,47 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
.video_sender
|
||||
.send(MediaData::RecordScreen(start, w, h, id));
|
||||
}
|
||||
Data::ElevateDirect => {
|
||||
let mut request = ElevationRequest::new();
|
||||
request.set_direct(true);
|
||||
let mut misc = Misc::new();
|
||||
misc.set_elevation_request(request);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
allow_err!(peer.send(&msg).await);
|
||||
self.elevation_requested = true;
|
||||
}
|
||||
Data::ElevateWithLogon(username, password) => {
|
||||
let mut request = ElevationRequest::new();
|
||||
request.set_logon(ElevationRequestWithLogon {
|
||||
username,
|
||||
password,
|
||||
..Default::default()
|
||||
});
|
||||
let mut misc = Misc::new();
|
||||
misc.set_elevation_request(request);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
allow_err!(peer.send(&msg).await);
|
||||
self.elevation_requested = true;
|
||||
}
|
||||
Data::NewVoiceCall => {
|
||||
let msg = new_voice_call_request(true);
|
||||
// Save the voice call request timestamp for the further validation.
|
||||
self.voice_call_request_timestamp = Some(
|
||||
NonZeroI64::new(msg.voice_call_request().req_timestamp)
|
||||
.unwrap_or(NonZeroI64::new(get_time()).unwrap()),
|
||||
);
|
||||
allow_err!(peer.send(&msg).await);
|
||||
self.handler.on_voice_call_waiting();
|
||||
}
|
||||
Data::CloseVoiceCall => {
|
||||
self.stop_voice_call();
|
||||
let msg = new_voice_call_request(false);
|
||||
self.handler
|
||||
.on_voice_call_closed("Closed manually by the peer");
|
||||
allow_err!(peer.send(&msg).await);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
true
|
||||
@@ -728,11 +817,11 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.handler.adapt_size();
|
||||
self.send_opts_after_login(peer).await;
|
||||
}
|
||||
let incomming_format = CodecFormat::from(&vf);
|
||||
if self.video_format != incomming_format {
|
||||
self.video_format = incomming_format.clone();
|
||||
let incoming_format = CodecFormat::from(&vf);
|
||||
if self.video_format != incoming_format {
|
||||
self.video_format = incoming_format.clone();
|
||||
self.handler.update_quality_status(QualityStatus {
|
||||
codec_format: Some(incomming_format),
|
||||
codec_format: Some(incoming_format),
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
@@ -752,22 +841,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)
|
||||
{
|
||||
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() {
|
||||
@@ -786,7 +884,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.handler.set_cursor_position(cp);
|
||||
}
|
||||
Some(message::Union::Clipboard(cb)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard.v {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
update_clipboard(cb, Some(&self.old_clipboard));
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
@@ -915,7 +1013,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
println!("error recving digest: {}", err);
|
||||
println!("error receiving digest: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -959,18 +1057,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;
|
||||
}
|
||||
@@ -989,8 +1094,13 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.handler.ui_handler.switch_display(&s);
|
||||
self.video_sender.send(MediaData::Reset).ok();
|
||||
if s.width > 0 && s.height > 0 {
|
||||
self.handler
|
||||
.set_display(s.x, s.y, s.width, s.height, s.cursor_embedded);
|
||||
self.handler.set_display(
|
||||
s.x,
|
||||
s.y,
|
||||
s.width,
|
||||
s.height,
|
||||
s.cursor_embedded,
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(misc::Union::CloseReason(c)) => {
|
||||
@@ -1003,40 +1113,99 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
Some(misc::Union::Uac(uac)) => {
|
||||
let msgtype = "custom-uac-nocancel";
|
||||
let title = "Prompt";
|
||||
let text = "Please wait for confirmation of UAC...";
|
||||
let link = "";
|
||||
if uac {
|
||||
self.handler.msgbox(msgtype, title, text, link);
|
||||
} else {
|
||||
self.handler
|
||||
.cancel_msgbox(
|
||||
&format!("{}-{}-{}-{}", msgtype, title, text, link,),
|
||||
#[cfg(feature = "flutter")]
|
||||
{
|
||||
if uac {
|
||||
self.handler.msgbox(
|
||||
"on-uac",
|
||||
"Prompt",
|
||||
"Please wait for confirmation of UAC...",
|
||||
"",
|
||||
);
|
||||
} else {
|
||||
self.handler.cancel_msgbox("on-uac");
|
||||
self.handler.cancel_msgbox("wait-uac");
|
||||
self.handler.cancel_msgbox("elevation-error");
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
{
|
||||
let msgtype = "custom-uac-nocancel";
|
||||
let title = "Prompt";
|
||||
let text = "Please wait for confirmation of UAC...";
|
||||
let link = "";
|
||||
if uac {
|
||||
self.handler.msgbox(msgtype, title, text, link);
|
||||
} else {
|
||||
self.handler.cancel_msgbox(&format!(
|
||||
"{}-{}-{}-{}",
|
||||
msgtype, title, text, link,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(misc::Union::ForegroundWindowElevated(elevated)) => {
|
||||
let msgtype = "custom-elevated-foreground-nocancel";
|
||||
let title = "Prompt";
|
||||
let text = "elevated_foreground_window_tip";
|
||||
let link = "";
|
||||
if elevated {
|
||||
self.handler.msgbox(msgtype, title, text, link);
|
||||
#[cfg(feature = "flutter")]
|
||||
{
|
||||
if elevated {
|
||||
self.handler.msgbox(
|
||||
"on-foreground-elevated",
|
||||
"Prompt",
|
||||
"elevated_foreground_window_tip",
|
||||
"",
|
||||
);
|
||||
} else {
|
||||
self.handler.cancel_msgbox("on-foreground-elevated");
|
||||
self.handler.cancel_msgbox("wait-uac");
|
||||
self.handler.cancel_msgbox("elevation-error");
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
{
|
||||
let msgtype = "custom-elevated-foreground-nocancel";
|
||||
let title = "Prompt";
|
||||
let text = "elevated_foreground_window_tip";
|
||||
let link = "";
|
||||
if elevated {
|
||||
self.handler.msgbox(msgtype, title, text, link);
|
||||
} else {
|
||||
self.handler.cancel_msgbox(&format!(
|
||||
"{}-{}-{}-{}",
|
||||
msgtype, title, text, link,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(misc::Union::ElevationResponse(err)) => {
|
||||
if err.is_empty() {
|
||||
self.handler.msgbox("wait-uac", "", "", "");
|
||||
} else {
|
||||
self.handler
|
||||
.cancel_msgbox(
|
||||
&format!("{}-{}-{}-{}", msgtype, title, text, link,),
|
||||
);
|
||||
.msgbox("elevation-error", "Elevation Error", &err, "");
|
||||
}
|
||||
}
|
||||
Some(misc::Union::PortableServiceRunning(b)) => {
|
||||
self.handler.portable_service_running(b);
|
||||
if self.elevation_requested && b {
|
||||
self.handler.msgbox(
|
||||
"custom-nocancel-success",
|
||||
"Successful",
|
||||
"Elevate successfully",
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(misc::Union::SwitchBack(_)) => {
|
||||
#[cfg(feature = "flutter")]
|
||||
self.handler.switch_back(&self.handler.id);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::TestDelay(t)) => {
|
||||
self.handler.handle_test_delay(t, peer).await;
|
||||
}
|
||||
Some(message::Union::AudioFrame(frame)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_audio {
|
||||
if !self.handler.lc.read().unwrap().disable_audio.v {
|
||||
self.audio_sender.send(MediaData::AudioFrame(frame)).ok();
|
||||
}
|
||||
}
|
||||
@@ -1061,6 +1230,40 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.handler
|
||||
.msgbox(&msgbox.msgtype, &msgbox.title, &msgbox.text, &link);
|
||||
}
|
||||
Some(message::Union::VoiceCallRequest(request)) => {
|
||||
if request.is_connect {
|
||||
// TODO: maybe we will do a voice call from the peer in the future.
|
||||
} else {
|
||||
log::debug!("The remote has requested to close the voice call");
|
||||
if let Some(sender) = self.stop_voice_call_sender.take() {
|
||||
allow_err!(sender.send(()));
|
||||
self.handler.on_voice_call_closed("");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::VoiceCallResponse(response)) => {
|
||||
let ts = std::mem::replace(&mut self.voice_call_request_timestamp, None);
|
||||
if let Some(ts) = ts {
|
||||
if response.req_timestamp != ts.get() {
|
||||
log::debug!("Possible encountering a voice call attack.");
|
||||
} else {
|
||||
if response.accepted {
|
||||
// The peer accepted the voice call.
|
||||
self.handler.on_voice_call_started();
|
||||
self.stop_voice_call_sender = self.start_voice_call();
|
||||
} else {
|
||||
// The peer refused the voice call.
|
||||
self.handler.on_voice_call_closed("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::PeerInfo(pi)) => match pi.conn_id {
|
||||
crate::SYNC_PEER_INFO_DISPLAYS => {
|
||||
self.handler.set_displays(&pi.displays);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1119,7 +1322,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
#[inline(always)]
|
||||
fn update_privacy_mode(&mut self, on: bool) {
|
||||
let mut config = self.handler.load_config();
|
||||
config.privacy_mode = on;
|
||||
config.privacy_mode.v = on;
|
||||
self.handler.save_config(config);
|
||||
|
||||
self.handler.update_privacy_mode();
|
||||
@@ -1192,15 +1395,15 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
fn check_clipboard_file_context(&self) {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let enabled = SERVER_FILE_TRANSFER_ENABLED.load(Ordering::SeqCst)
|
||||
&& self.handler.lc.read().unwrap().enable_file_transfer;
|
||||
let enabled = *self.handler.server_file_transfer_enabled.read().unwrap()
|
||||
&& self.handler.lc.read().unwrap().enable_file_transfer.v;
|
||||
ContextSend::enable(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn handle_cliprdr_msg(&self, clip: hbb_common::message_proto::Cliprdr) {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard.v {
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
|
||||
if self.client_conn_id
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use clipboard::ClipbaordFile;
|
||||
use clipboard::ClipboardFile;
|
||||
use hbb_common::message_proto::*;
|
||||
|
||||
pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
pub fn clip_2_msg(clip: ClipboardFile) -> Message {
|
||||
match clip {
|
||||
ClipbaordFile::MonitorReady => Message {
|
||||
ClipboardFile::MonitorReady => Message {
|
||||
union: Some(message::Union::Cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::Ready(CliprdrMonitorReady {
|
||||
..Default::default()
|
||||
@@ -12,7 +12,7 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
ClipbaordFile::FormatList { format_list } => {
|
||||
ClipboardFile::FormatList { format_list } => {
|
||||
let mut formats: Vec<CliprdrFormat> = Vec::new();
|
||||
for v in format_list.iter() {
|
||||
formats.push(CliprdrFormat {
|
||||
@@ -32,7 +32,7 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
ClipbaordFile::FormatListResponse { msg_flags } => Message {
|
||||
ClipboardFile::FormatListResponse { msg_flags } => Message {
|
||||
union: Some(message::Union::Cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::FormatListResponse(
|
||||
CliprdrServerFormatListResponse {
|
||||
@@ -44,7 +44,7 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
ClipbaordFile::FormatDataRequest {
|
||||
ClipboardFile::FormatDataRequest {
|
||||
requested_format_id,
|
||||
} => Message {
|
||||
union: Some(message::Union::Cliprdr(Cliprdr {
|
||||
@@ -58,7 +58,7 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
ClipbaordFile::FormatDataResponse {
|
||||
ClipboardFile::FormatDataResponse {
|
||||
msg_flags,
|
||||
format_data,
|
||||
} => Message {
|
||||
@@ -74,7 +74,7 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
ClipbaordFile::FileContentsRequest {
|
||||
ClipboardFile::FileContentsRequest {
|
||||
stream_id,
|
||||
list_index,
|
||||
dw_flags,
|
||||
@@ -102,7 +102,7 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
ClipbaordFile::FileContentsResponse {
|
||||
ClipboardFile::FileContentsResponse {
|
||||
msg_flags,
|
||||
stream_id,
|
||||
requested_data,
|
||||
@@ -123,28 +123,28 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipbaordFile> {
|
||||
pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipboardFile> {
|
||||
match msg.union {
|
||||
Some(cliprdr::Union::Ready(_)) => Some(ClipbaordFile::MonitorReady),
|
||||
Some(cliprdr::Union::Ready(_)) => Some(ClipboardFile::MonitorReady),
|
||||
Some(cliprdr::Union::FormatList(data)) => {
|
||||
let mut format_list: Vec<(i32, String)> = Vec::new();
|
||||
for v in data.formats.iter() {
|
||||
format_list.push((v.id, v.format.clone()));
|
||||
}
|
||||
Some(ClipbaordFile::FormatList { format_list })
|
||||
Some(ClipboardFile::FormatList { format_list })
|
||||
}
|
||||
Some(cliprdr::Union::FormatListResponse(data)) => Some(ClipbaordFile::FormatListResponse {
|
||||
Some(cliprdr::Union::FormatListResponse(data)) => Some(ClipboardFile::FormatListResponse {
|
||||
msg_flags: data.msg_flags,
|
||||
}),
|
||||
Some(cliprdr::Union::FormatDataRequest(data)) => Some(ClipbaordFile::FormatDataRequest {
|
||||
Some(cliprdr::Union::FormatDataRequest(data)) => Some(ClipboardFile::FormatDataRequest {
|
||||
requested_format_id: data.requested_format_id,
|
||||
}),
|
||||
Some(cliprdr::Union::FormatDataResponse(data)) => Some(ClipbaordFile::FormatDataResponse {
|
||||
Some(cliprdr::Union::FormatDataResponse(data)) => Some(ClipboardFile::FormatDataResponse {
|
||||
msg_flags: data.msg_flags,
|
||||
format_data: data.format_data.into(),
|
||||
}),
|
||||
Some(cliprdr::Union::FileContentsRequest(data)) => {
|
||||
Some(ClipbaordFile::FileContentsRequest {
|
||||
Some(ClipboardFile::FileContentsRequest {
|
||||
stream_id: data.stream_id,
|
||||
list_index: data.list_index,
|
||||
dw_flags: data.dw_flags,
|
||||
@@ -156,7 +156,7 @@ pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipbaordFile> {
|
||||
})
|
||||
}
|
||||
Some(cliprdr::Union::FileContentsResponse(data)) => {
|
||||
Some(ClipbaordFile::FileContentsResponse {
|
||||
Some(ClipboardFile::FileContentsResponse {
|
||||
msg_flags: data.msg_flags,
|
||||
stream_id: data.stream_id,
|
||||
requested_data: data.requested_data.into(),
|
||||
|
||||
118
src/common.rs
118
src/common.rs
@@ -30,11 +30,15 @@ use hbb_common::{
|
||||
// #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all};
|
||||
|
||||
use crate::ui_interface::{get_option, set_option};
|
||||
|
||||
pub type NotifyMessageBox = fn(String, String, String, String) -> dyn Future<Output = ()>;
|
||||
|
||||
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";
|
||||
|
||||
@@ -48,11 +52,16 @@ 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")]
|
||||
{
|
||||
if !scrap::is_x11() {
|
||||
crate::server::wayland::set_wayland_scrap_map_err();
|
||||
if !*IS_X11 {
|
||||
crate::server::wayland::init();
|
||||
}
|
||||
}
|
||||
true
|
||||
@@ -92,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 {
|
||||
@@ -105,6 +118,54 @@ pub fn check_clipboard(
|
||||
None
|
||||
}
|
||||
|
||||
/// Set sound input device.
|
||||
pub fn set_sound_input(device: String) {
|
||||
let prior_device = get_option("audio-input".to_owned());
|
||||
if prior_device != device {
|
||||
log::info!("switch to audio input device {}", device);
|
||||
std::thread::spawn(move || {
|
||||
set_option("audio-input".to_owned(), device);
|
||||
});
|
||||
} else {
|
||||
log::info!("audio input is already set to {}", device);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get system's default sound input device name.
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn get_default_sound_input() -> Option<String> {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
use cpal::traits::{DeviceTrait, HostTrait};
|
||||
let host = cpal::default_host();
|
||||
let dev = host.default_input_device();
|
||||
return if let Some(dev) = dev {
|
||||
match dev.name() {
|
||||
Ok(name) => Some(name),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let input = crate::platform::linux::get_default_pa_source();
|
||||
return if let Some(input) = input {
|
||||
Some(input.1)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub fn get_default_sound_input() -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn update_clipboard(clipboard: Clipboard, old: Option<&Arc<Mutex<String>>>) {
|
||||
let content = if clipboard.compress {
|
||||
@@ -122,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);
|
||||
}
|
||||
@@ -451,6 +513,7 @@ pub fn run_me<T: AsRef<std::ffi::OsStr>>(args: Vec<T>) -> std::io::Result<std::p
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn username() -> String {
|
||||
// fix bug of whoami
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -459,6 +522,14 @@ pub fn username() -> String {
|
||||
return DEVICE_NAME.lock().unwrap().clone();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hostname() -> String {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
return whoami::hostname();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
return DEVICE_NAME.lock().unwrap().clone();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_port<T: std::string::ToString>(host: T, port: i32) -> String {
|
||||
hbb_common::socket_client::check_port(host, port)
|
||||
@@ -529,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()
|
||||
}
|
||||
@@ -581,9 +647,9 @@ pub fn get_api_server(api: String, custom: String) -> String {
|
||||
if !s0.is_empty() {
|
||||
let s = crate::increase_port(&s0, -2);
|
||||
if s == s0 {
|
||||
format!("http://{}:{}", s, config::RENDEZVOUS_PORT - 2);
|
||||
return format!("http://{}:{}", s, config::RENDEZVOUS_PORT - 2);
|
||||
} else {
|
||||
format!("http://{}", s);
|
||||
return format!("http://{}", s);
|
||||
}
|
||||
}
|
||||
"https://admin.rustdesk.com".to_owned()
|
||||
@@ -658,15 +724,31 @@ pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Mess
|
||||
msg_out
|
||||
}
|
||||
|
||||
pub fn is_keyboard_mode_supported(keyboard_mode: &KeyboardMode, version_number: i64) -> bool {
|
||||
match keyboard_mode {
|
||||
KeyboardMode::Legacy => true,
|
||||
KeyboardMode::Map => version_number >= hbb_common::get_version_number("1.2.0"),
|
||||
KeyboardMode::Translate => version_number >= hbb_common::get_version_number("1.2.0"),
|
||||
KeyboardMode::Auto => version_number >= hbb_common::get_version_number("1.2.0"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_supported_keyboard_modes(version: i64) -> Vec<KeyboardMode> {
|
||||
KeyboardMode::iter()
|
||||
.filter(|&mode| is_keyboard_mode_supported(mode, version))
|
||||
.map(|&mode| mode)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref IS_X11: Mutex<bool> = Mutex::new(false);
|
||||
pub static ref IS_X11: bool = false;
|
||||
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref IS_X11: Mutex<bool> = Mutex::new("x11" == hbb_common::platform::linux::get_display_server());
|
||||
pub static ref IS_X11: bool = "x11" == hbb_common::platform::linux::get_display_server();
|
||||
}
|
||||
|
||||
pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> String {
|
||||
@@ -688,7 +770,13 @@ pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> Strin
|
||||
serde_json::to_string(&fd_json).unwrap_or("".into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_common {
|
||||
use super::*;
|
||||
/// 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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -54,11 +57,6 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
return core_main_invoke_new_connection(std::env::args());
|
||||
}
|
||||
let click_setup = cfg!(windows) && args.is_empty() && crate::common::is_setup(&arg_exe);
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
{
|
||||
_is_quick_support =
|
||||
cfg!(windows) && args.is_empty() && arg_exe.to_lowercase().ends_with("qs.exe");
|
||||
}
|
||||
if click_setup {
|
||||
args.push("--install".to_owned());
|
||||
flutter_args.push("--install".to_string());
|
||||
@@ -70,6 +68,14 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
println!("{}", crate::VERSION);
|
||||
return None;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
_is_quick_support |= !crate::platform::is_installed()
|
||||
&& args.is_empty()
|
||||
&& (arg_exe.to_lowercase().ends_with("qs.exe")
|
||||
|| (!click_setup && crate::platform::is_elevated(None).unwrap_or(false)));
|
||||
crate::portable_service::client::set_quick_support(_is_quick_support);
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
use hbb_common::env_logger::*;
|
||||
@@ -106,7 +112,8 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
&& !_is_elevate
|
||||
&& !_is_run_as_system
|
||||
{
|
||||
if let Err(e) = crate::portable_service::client::start_portable_service() {
|
||||
use crate::portable_service::client;
|
||||
if let Err(e) = client::start_portable_service(client::StartPara::Direct) {
|
||||
log::error!("Failed to start portable service:{:?}", e);
|
||||
}
|
||||
}
|
||||
@@ -160,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,
|
||||
@@ -179,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::tray::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());
|
||||
}
|
||||
@@ -247,11 +241,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
} else if args[0] == "--cm" {
|
||||
// call connection manager to establish connections
|
||||
// meanwhile, return true to call flutter window to show control panel
|
||||
#[cfg(feature = "flutter")]
|
||||
crate::flutter::connection_manager::start_listen_ipc_thread();
|
||||
crate::ui_interface::start_option_status_sync();
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::platform::macos::hide_dock();
|
||||
}
|
||||
}
|
||||
//_async_logger_holder.map(|x| x.flush());
|
||||
@@ -297,18 +287,37 @@ fn import_config(path: &str) {
|
||||
fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<String>> {
|
||||
args.position(|element| {
|
||||
return element == "--connect";
|
||||
})
|
||||
.unwrap();
|
||||
})?;
|
||||
let peer_id = args.next().unwrap_or("".to_string());
|
||||
if peer_id.is_empty() {
|
||||
eprintln!("please provide a valid peer id");
|
||||
return None;
|
||||
}
|
||||
let mut switch_uuid = None;
|
||||
while let Some(item) = args.next() {
|
||||
if item == "--switch_uuid" {
|
||||
switch_uuid = args.next();
|
||||
}
|
||||
}
|
||||
let mut param_array = vec![];
|
||||
if switch_uuid.is_some() {
|
||||
let switch_uuid = switch_uuid.map_or("".to_string(), |p| format!("switch_uuid={}", p));
|
||||
param_array.push(switch_uuid);
|
||||
}
|
||||
|
||||
let params = param_array.join("&");
|
||||
let params_flag = if params.is_empty() { "" } else { "?" };
|
||||
#[allow(unused)]
|
||||
let uni_links = format!(
|
||||
"rustdesk://connection/new/{}{}{}",
|
||||
peer_id, params_flag, params
|
||||
);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use crate::dbus::invoke_new_connection;
|
||||
|
||||
match invoke_new_connection(peer_id) {
|
||||
match invoke_new_connection(uni_links) {
|
||||
Ok(()) => {
|
||||
return None;
|
||||
}
|
||||
@@ -322,16 +331,21 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<Strin
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use winapi::um::winuser::WM_USER;
|
||||
let uni_links = format!("rustdesk://connection/new/{}", peer_id);
|
||||
let res = crate::platform::send_message_to_hnwd(
|
||||
"FLUTTER_RUNNER_WIN32_WINDOW",
|
||||
"RustDesk",
|
||||
(WM_USER + 2) as _, // referred from unilinks desktop pub
|
||||
uni_links.as_str(),
|
||||
true,
|
||||
false,
|
||||
);
|
||||
return if res { None } else { Some(Vec::new()) };
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
return Some(Vec::new());
|
||||
{
|
||||
return if let Err(_) = crate::ipc::send_url_scheme(uni_links) {
|
||||
Some(Vec::new())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
433
src/flutter.rs
433
src/flutter.rs
@@ -1,3 +1,24 @@
|
||||
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, 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,
|
||||
@@ -5,29 +26,33 @@ use std::{
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
|
||||
|
||||
use hbb_common::{
|
||||
bail, config::LocalConfig, get_version_number, message_proto::*, rendezvous_proto::ConnType,
|
||||
ResultType,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::ui_session_interface::{io_loop, InvokeUiSession, Session};
|
||||
|
||||
use crate::{client::*, flutter_ffi::EventToUI};
|
||||
|
||||
pub(super) const APP_TYPE_MAIN: &str = "main";
|
||||
pub(super) const APP_TYPE_CM: &str = "cm";
|
||||
pub(super) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
|
||||
pub(super) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
||||
pub(super) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CUR_SESSION_ID: RwLock<String> = Default::default();
|
||||
pub static ref CUR_SESSION_ID: RwLock<String> = Default::default();
|
||||
pub static ref SESSIONS: RwLock<HashMap<String, Session<FlutterHandler>>> = Default::default();
|
||||
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,6 +64,12 @@ pub extern "C" fn rustdesk_core_main() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn handle_applicationShouldOpenUntitledFile() {
|
||||
crate::platform::macos::handle_application_should_open_untitled_file();
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rustdesk_core_main_args(args_len: *mut c_int) -> *mut *mut c_char {
|
||||
@@ -101,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 {
|
||||
@@ -123,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 {
|
||||
@@ -282,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 });
|
||||
@@ -309,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![
|
||||
@@ -320,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) {
|
||||
@@ -349,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![
|
||||
@@ -357,7 +540,18 @@ impl InvokeUiSession for FlutterHandler {
|
||||
("y", &display.y.to_string()),
|
||||
("width", &display.width.to_string()),
|
||||
("height", &display.height.to_string()),
|
||||
("cursor_embedded", &{if display.cursor_embedded {1} else {0}}.to_string()),
|
||||
(
|
||||
"cursor_embedded",
|
||||
&{
|
||||
if display.cursor_embedded {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
.to_string(),
|
||||
),
|
||||
("resolutions", &resolutions),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -373,6 +567,48 @@ impl InvokeUiSession for FlutterHandler {
|
||||
fn clipboard(&self, content: String) {
|
||||
self.push_event("clipboard", vec![("content", &content)]);
|
||||
}
|
||||
|
||||
fn switch_back(&self, peer_id: &str) {
|
||||
self.push_event("switch_back", [("peer_id", peer_id)].into());
|
||||
}
|
||||
|
||||
fn portable_service_running(&self, running: bool) {
|
||||
self.push_event(
|
||||
"portable_service_running",
|
||||
[("running", running.to_string().as_str())].into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn on_voice_call_started(&self) {
|
||||
self.push_event("on_voice_call_started", [].into());
|
||||
}
|
||||
|
||||
fn on_voice_call_closed(&self, reason: &str) {
|
||||
self.push_event("on_voice_call_closed", [("reason", reason)].into())
|
||||
}
|
||||
|
||||
fn on_voice_call_waiting(&self) {
|
||||
self.push_event("on_voice_call_waiting", [].into());
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -382,12 +618,21 @@ impl InvokeUiSession for FlutterHandler {
|
||||
/// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+
|
||||
/// * `is_file_transfer` - If the session is used for file transfer.
|
||||
/// * `is_port_forward` - If the session is used for port forward.
|
||||
pub fn session_add(id: &str, is_file_transfer: bool, is_port_forward: bool) -> ResultType<()> {
|
||||
pub fn session_add(
|
||||
id: &str,
|
||||
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()
|
||||
};
|
||||
|
||||
@@ -400,11 +645,17 @@ pub fn session_add(id: &str, is_file_transfer: bool, is_port_forward: bool) -> R
|
||||
ConnType::DEFAULT_CONN
|
||||
};
|
||||
|
||||
let switch_uuid = if switch_uuid.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(switch_uuid.to_string())
|
||||
};
|
||||
|
||||
session
|
||||
.lc
|
||||
.write()
|
||||
.unwrap()
|
||||
.initialize(session_id, conn_type);
|
||||
.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();
|
||||
@@ -421,6 +672,13 @@ pub fn session_add(id: &str, is_file_transfer: bool, is_port_forward: bool) -> R
|
||||
/// * `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 || {
|
||||
@@ -432,6 +690,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 {
|
||||
@@ -489,6 +772,11 @@ pub mod connection_manager {
|
||||
fn show_elevation(&self, show: bool) {
|
||||
self.push_event("show_elevation", vec![("show", &show.to_string())]);
|
||||
}
|
||||
|
||||
fn update_voice_call_state(&self, client: &crate::ui_cm_interface::Client) {
|
||||
let client_json = serde_json::to_string(&client).unwrap_or("".into());
|
||||
self.push_event("update_voice_call_state", vec![("client", &client_json)]);
|
||||
}
|
||||
}
|
||||
|
||||
impl FlutterHandler {
|
||||
@@ -497,12 +785,14 @@ pub mod connection_manager {
|
||||
assert!(h.get("name").is_none());
|
||||
h.insert("name", name);
|
||||
|
||||
if let Some(s) = GLOBAL_EVENT_STREAM
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(super::APP_TYPE_MAIN)
|
||||
{
|
||||
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
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -581,3 +871,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) {}
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
#[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,
|
||||
};
|
||||
|
||||
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
|
||||
use serde_json::json;
|
||||
|
||||
use hbb_common::ResultType;
|
||||
use hbb_common::{
|
||||
config::{self, LocalConfig, PeerConfig, ONLINE},
|
||||
fs, log,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
// use crate::hbbs_http::account::AuthResult;
|
||||
|
||||
use crate::flutter::{self, SESSIONS};
|
||||
use crate::ui_interface::{self, *};
|
||||
use crate::{
|
||||
client::file_trait::FileManager,
|
||||
common::make_fd_to_json,
|
||||
flutter::{session_add, session_start_},
|
||||
};
|
||||
fn initialize(app_dir: &str) {
|
||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||
#[cfg(target_os = "android")]
|
||||
@@ -46,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<()> {
|
||||
@@ -81,8 +84,16 @@ pub fn session_add_sync(
|
||||
id: String,
|
||||
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) {
|
||||
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())
|
||||
@@ -129,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) {
|
||||
@@ -147,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,8 +258,14 @@ pub fn session_get_keyboard_mode(id: String) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn session_set_keyboard_mode(id: String, value: String) {
|
||||
let mut _mode_updated = false;
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
|
||||
session.save_keyboard_mode(value);
|
||||
_mode_updated = true;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if _mode_updated {
|
||||
crate::keyboard::update_grab_get_key_name();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +277,21 @@ pub fn session_get_custom_image_quality(id: String) -> Option<Vec<i32>> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_is_keyboard_mode_supported(id: String, mode: String) -> SyncReturn<bool> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
if let Ok(mode) = KeyboardMode::from_str(&mode[..]) {
|
||||
SyncReturn(is_keyboard_mode_supported(
|
||||
&mode,
|
||||
session.get_peer_version(),
|
||||
))
|
||||
} else {
|
||||
SyncReturn(false)
|
||||
}
|
||||
} else {
|
||||
SyncReturn(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_custom_image_quality(id: String, value: i32) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
|
||||
session.save_custom_image_quality(value);
|
||||
@@ -289,10 +327,11 @@ pub fn session_handle_flutter_key_event(
|
||||
name: String,
|
||||
keycode: i32,
|
||||
scancode: i32,
|
||||
lock_modes: i32,
|
||||
down_or_up: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.handle_flutter_key_event(&name, keycode, scancode, down_or_up);
|
||||
session.handle_flutter_key_event(&name, keycode, scancode, lock_modes, down_or_up);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +339,6 @@ pub fn session_enter_or_leave(id: String, enter: bool) {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
if enter {
|
||||
crate::keyboard::set_cur_session(session.clone());
|
||||
session.enter();
|
||||
} else {
|
||||
session.leave();
|
||||
@@ -473,6 +511,37 @@ pub fn session_resume_job(id: String, act_id: i32, is_remote: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_elevate_direct(id: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.elevate_direct();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_elevate_with_logon(id: String, username: String, password: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.elevate_with_logon(username, password);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_switch_sides(id: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.switch_sides();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -480,6 +549,17 @@ pub fn main_get_sound_inputs() -> Vec<String> {
|
||||
vec![String::from("")]
|
||||
}
|
||||
|
||||
pub fn main_get_default_sound_input() -> Option<String> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
return get_default_sound_input();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
None
|
||||
}
|
||||
|
||||
pub fn main_get_hostname() -> SyncReturn<String> {
|
||||
SyncReturn(crate::common::hostname())
|
||||
}
|
||||
|
||||
pub fn main_change_id(new_id: String) {
|
||||
change_id(new_id)
|
||||
}
|
||||
@@ -588,10 +668,6 @@ pub fn main_discover() {
|
||||
discover();
|
||||
}
|
||||
|
||||
pub fn main_has_rendezvous_service() -> bool {
|
||||
has_rendezvous_service()
|
||||
}
|
||||
|
||||
pub fn main_get_api_server() -> String {
|
||||
get_api_server()
|
||||
}
|
||||
@@ -650,6 +726,10 @@ pub fn main_peer_has_password(id: String) -> bool {
|
||||
peer_has_password(id)
|
||||
}
|
||||
|
||||
pub fn main_is_in_recent_peers(id: String) -> bool {
|
||||
PeerConfig::peers().iter().any(|e| e.0 == id)
|
||||
}
|
||||
|
||||
pub fn main_load_recent_peers() {
|
||||
if !config::APP_DIR.read().unwrap().is_empty() {
|
||||
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers()
|
||||
@@ -720,6 +800,10 @@ pub fn main_load_lan_peers() {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn main_remove_discovered(id: String) {
|
||||
remove_discovered(id);
|
||||
}
|
||||
|
||||
fn main_broadcast_message(data: &HashMap<&str, &str>) {
|
||||
let apps = vec![
|
||||
flutter::APP_TYPE_DESKTOP_REMOTE,
|
||||
@@ -748,6 +832,18 @@ pub fn main_default_video_save_directory() -> String {
|
||||
default_video_save_directory()
|
||||
}
|
||||
|
||||
pub fn main_set_user_default_option(key: String, value: String) {
|
||||
set_user_default_option(key, value);
|
||||
}
|
||||
|
||||
pub fn main_get_user_default_option(key: String) -> SyncReturn<String> {
|
||||
SyncReturn(get_user_default_option(key))
|
||||
}
|
||||
|
||||
pub fn main_handle_relay_id(id: String) -> String {
|
||||
handle_relay_id(id)
|
||||
}
|
||||
|
||||
pub fn session_add_port_forward(
|
||||
id: String,
|
||||
local_port: i32,
|
||||
@@ -771,6 +867,26 @@ pub fn session_new_rdp(id: String) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_request_voice_call(id: String) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
|
||||
session.request_voice_call();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_close_voice_call(id: String) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
|
||||
session.close_voice_call();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cm_handle_incoming_voice_call(id: i32, accept: bool) {
|
||||
crate::ui_cm_interface::handle_incoming_voice_call(id, accept);
|
||||
}
|
||||
|
||||
pub fn cm_close_voice_call(id: i32) {
|
||||
crate::ui_cm_interface::close_voice_call(id);
|
||||
}
|
||||
|
||||
pub fn main_get_last_remote_id() -> String {
|
||||
LocalConfig::get_remote_id()
|
||||
}
|
||||
@@ -1039,11 +1155,8 @@ pub fn cm_elevate_portable(conn_id: i32) {
|
||||
crate::ui_cm_interface::elevate_portable(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 cm_switch_back(conn_id: i32) {
|
||||
crate::ui_cm_interface::switch_back(conn_id);
|
||||
}
|
||||
|
||||
pub fn main_get_build_date() -> String {
|
||||
@@ -1081,8 +1194,8 @@ pub fn query_onlines(ids: Vec<String>) {
|
||||
crate::rendezvous_mediator::query_online_states(ids, handle_query_onlines)
|
||||
}
|
||||
|
||||
pub fn version_to_number(v: String) -> i64 {
|
||||
hbb_common::get_version_number(&v)
|
||||
pub fn version_to_number(v: String) -> SyncReturn<i64> {
|
||||
SyncReturn(hbb_common::get_version_number(&v))
|
||||
}
|
||||
|
||||
pub fn option_synced() -> bool {
|
||||
@@ -1093,8 +1206,16 @@ pub fn main_is_installed() -> SyncReturn<bool> {
|
||||
SyncReturn(is_installed())
|
||||
}
|
||||
|
||||
pub fn main_start_grab_keyboard() {
|
||||
pub fn main_start_grab_keyboard() -> SyncReturn<bool> {
|
||||
#[cfg(target_os = "linux")]
|
||||
if !*crate::common::IS_X11 {
|
||||
return SyncReturn(false);
|
||||
}
|
||||
crate::keyboard::client::start_grab_loop();
|
||||
if !is_can_input_monitoring(false) {
|
||||
return SyncReturn(false);
|
||||
}
|
||||
SyncReturn(true)
|
||||
}
|
||||
|
||||
pub fn main_is_installed_lower_version() -> SyncReturn<bool> {
|
||||
@@ -1125,6 +1246,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)
|
||||
@@ -1140,7 +1265,9 @@ pub fn main_update_me() -> SyncReturn<bool> {
|
||||
}
|
||||
|
||||
pub fn set_cur_session_id(id: String) {
|
||||
super::flutter::set_cur_session_id(id)
|
||||
super::flutter::set_cur_session_id(id);
|
||||
#[cfg(windows)]
|
||||
crate::keyboard::update_grab_get_key_name();
|
||||
}
|
||||
|
||||
pub fn install_show_run_without_install() -> SyncReturn<bool> {
|
||||
@@ -1187,27 +1314,85 @@ pub fn main_is_login_wayland() -> SyncReturn<bool> {
|
||||
SyncReturn(is_login_wayland())
|
||||
}
|
||||
|
||||
pub fn main_start_pa() {
|
||||
#[cfg(target_os = "linux")]
|
||||
std::thread::spawn(crate::ipc::start_pa);
|
||||
}
|
||||
|
||||
pub fn main_hide_docker() -> SyncReturn<bool> {
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::platform::macos::hide_dock();
|
||||
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();
|
||||
}
|
||||
|
||||
/// Start an ipc server for receiving the url scheme.
|
||||
///
|
||||
/// * Should only be called in the main flutter window.
|
||||
/// * macOS only
|
||||
pub fn main_start_ipc_url_server() {
|
||||
#[cfg(target_os = "macos")]
|
||||
std::thread::spawn(move || crate::server::start_ipc_url_server());
|
||||
}
|
||||
|
||||
/// Send a url scheme throught the ipc.
|
||||
///
|
||||
/// * macOS only
|
||||
#[allow(unused_variables)]
|
||||
pub fn send_url_scheme(_url: String) {
|
||||
#[cfg(target_os = "macos")]
|
||||
std::thread::spawn(move || crate::handle_url_scheme(_url));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use hbb_common::{log, config};
|
||||
use jni::{
|
||||
objects::{JClass, JString},
|
||||
sys::jstring,
|
||||
JNIEnv,
|
||||
};
|
||||
|
||||
use hbb_common::log;
|
||||
|
||||
use crate::start_server;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
app_dir: JString,
|
||||
) {
|
||||
log::debug!("startServer from java");
|
||||
log::debug!("startServer from jvm");
|
||||
if let Ok(app_dir) = env.get_string(app_dir) {
|
||||
*config::APP_DIR.write().unwrap() = app_dir.into();
|
||||
}
|
||||
std::thread::spawn(move || start_server(true));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startService(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
) {
|
||||
log::debug!("startService from jvm");
|
||||
config::Config::set_option("stop-service".into(), "".into());
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale(
|
||||
env: JNIEnv,
|
||||
|
||||
@@ -5,6 +5,7 @@ use serde_json::{Map, Value};
|
||||
#[cfg(feature = "flutter")]
|
||||
pub mod account;
|
||||
pub mod record_upload;
|
||||
pub mod sync;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HbbHttpResponse<T> {
|
||||
|
||||
116
src/hbbs_http/sync.rs
Normal file
116
src/hbbs_http/sync.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use std::{collections::HashMap, sync::Mutex, time::Duration};
|
||||
|
||||
use hbb_common::{
|
||||
config::{Config, LocalConfig},
|
||||
tokio::{self, sync::broadcast, time::Instant},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::Connection;
|
||||
|
||||
const TIME_HEARTBEAT: Duration = Duration::from_secs(30);
|
||||
const TIME_CONN: Duration = Duration::from_secs(3);
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref SENDER : Mutex<broadcast::Sender<Vec<i32>>> = Mutex::new(start_hbbs_sync());
|
||||
}
|
||||
|
||||
pub fn start() {
|
||||
let _sender = SENDER.lock().unwrap();
|
||||
}
|
||||
|
||||
pub fn signal_receiver() -> broadcast::Receiver<Vec<i32>> {
|
||||
SENDER.lock().unwrap().subscribe()
|
||||
}
|
||||
|
||||
fn start_hbbs_sync() -> broadcast::Sender<Vec<i32>> {
|
||||
let (tx, _rx) = broadcast::channel::<Vec<i32>>(16);
|
||||
std::thread::spawn(move || start_hbbs_sync_async());
|
||||
return tx;
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct StrategyOptions {
|
||||
pub config_options: HashMap<String, String>,
|
||||
pub extra: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn start_hbbs_sync_async() {
|
||||
tokio::spawn(async move {
|
||||
let mut interval = tokio::time::interval_at(Instant::now() + TIME_CONN, TIME_CONN);
|
||||
let mut last_send = Instant::now();
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = interval.tick() => {
|
||||
let url = heartbeat_url();
|
||||
let modified_at = LocalConfig::get_option("strategy_timestamp").parse::<i64>().unwrap_or(0);
|
||||
if !url.is_empty() {
|
||||
let conns = Connection::alive_conns();
|
||||
if conns.is_empty() && last_send.elapsed() < TIME_HEARTBEAT {
|
||||
continue;
|
||||
}
|
||||
last_send = Instant::now();
|
||||
let mut v = Value::default();
|
||||
v["id"] = json!(Config::get_id());
|
||||
v["ver"] = json!(hbb_common::get_version_number(crate::VERSION));
|
||||
if !conns.is_empty() {
|
||||
v["conns"] = json!(conns);
|
||||
}
|
||||
v["modified_at"] = json!(modified_at);
|
||||
if let Ok(s) = crate::post_request(url.clone(), v.to_string(), "").await {
|
||||
if let Ok(mut rsp) = serde_json::from_str::<HashMap::<&str, Value>>(&s) {
|
||||
if let Some(conns) = rsp.remove("disconnect") {
|
||||
if let Ok(conns) = serde_json::from_value::<Vec<i32>>(conns) {
|
||||
SENDER.lock().unwrap().send(conns).ok();
|
||||
}
|
||||
}
|
||||
if let Some(rsp_modified_at) = rsp.remove("modified_at") {
|
||||
if let Ok(rsp_modified_at) = serde_json::from_value::<i64>(rsp_modified_at) {
|
||||
if rsp_modified_at != modified_at {
|
||||
LocalConfig::set_option("strategy_timestamp".to_string(), rsp_modified_at.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(strategy) = rsp.remove("strategy") {
|
||||
if let Ok(strategy) = serde_json::from_value::<StrategyOptions>(strategy) {
|
||||
handle_config_options(strategy.config_options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn heartbeat_url() -> String {
|
||||
let url = crate::common::get_api_server(
|
||||
Config::get_option("api-server"),
|
||||
Config::get_option("custom-rendezvous-server"),
|
||||
);
|
||||
if url.is_empty() || url.contains("rustdesk.com") {
|
||||
return "".to_owned();
|
||||
}
|
||||
format!("{}/api/heartbeat", url)
|
||||
}
|
||||
|
||||
fn handle_config_options(config_options: HashMap<String, String>) {
|
||||
let mut options = Config::get_options();
|
||||
config_options
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
if v.is_empty() {
|
||||
options.remove(k);
|
||||
} else {
|
||||
options.insert(k.to_string(), v.to_string());
|
||||
}
|
||||
})
|
||||
.count();
|
||||
Config::set_options(options);
|
||||
}
|
||||
51
src/ipc.rs
51
src/ipc.rs
@@ -9,7 +9,7 @@ use parity_tokio_ipc::{
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use clipboard::ClipbaordFile;
|
||||
pub use clipboard::ClipboardFile;
|
||||
use hbb_common::{
|
||||
allow_err, bail, bytes,
|
||||
bytes_codec::BytesCodec,
|
||||
@@ -166,6 +166,7 @@ pub enum Data {
|
||||
file_transfer_enabled: bool,
|
||||
restart: bool,
|
||||
recording: bool,
|
||||
from_switch: bool,
|
||||
},
|
||||
ChatMessage {
|
||||
text: String,
|
||||
@@ -189,9 +190,9 @@ pub enum Data {
|
||||
Socks(Option<config::Socks5Server>),
|
||||
FS(FS),
|
||||
Test,
|
||||
SyncConfig(Option<(Config, Config2)>),
|
||||
SyncConfig(Option<Box<(Config, Config2)>>),
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
ClipbaordFile(ClipbaordFile),
|
||||
ClipboardFile(ClipboardFile),
|
||||
ClipboardFileEnabled(bool),
|
||||
PrivacyModeState((i32, PrivacyModeState)),
|
||||
TestRendezvousServer,
|
||||
@@ -207,6 +208,13 @@ pub enum Data {
|
||||
Empty,
|
||||
Disconnected,
|
||||
DataPortableService(DataPortableService),
|
||||
SwitchSidesRequest(String),
|
||||
SwitchSidesBack,
|
||||
UrlLink(String),
|
||||
VoiceCallIncoming,
|
||||
StartVoiceCall,
|
||||
VoiceCallResponse(bool),
|
||||
CloseVoiceCall(String),
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
@@ -411,7 +419,8 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
let t = Config::get_nat_type();
|
||||
allow_err!(stream.send(&Data::NatType(Some(t))).await);
|
||||
}
|
||||
Data::SyncConfig(Some((config, config2))) => {
|
||||
Data::SyncConfig(Some(configs)) => {
|
||||
let (config, config2) = *configs;
|
||||
let _chk = CheckIfRestart::new();
|
||||
Config::set(config);
|
||||
Config2::set(config2);
|
||||
@@ -420,13 +429,24 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
Data::SyncConfig(None) => {
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&Data::SyncConfig(Some((Config::get(), Config2::get()))))
|
||||
.send(&Data::SyncConfig(Some(
|
||||
(Config::get(), Config2::get()).into()
|
||||
)))
|
||||
.await
|
||||
);
|
||||
}
|
||||
Data::TestRendezvousServer => {
|
||||
crate::test_rendezvous_server();
|
||||
}
|
||||
Data::SwitchSidesRequest(id) => {
|
||||
let uuid = uuid::Uuid::new_v4();
|
||||
crate::server::insert_switch_sides_uuid(id, uuid.clone());
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&Data::SwitchSidesRequest(uuid.to_string()))
|
||||
.await
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -529,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()) {
|
||||
@@ -820,3 +840,22 @@ pub async fn test_rendezvous_server() -> ResultType<()> {
|
||||
c.send(&Data::TestRendezvousServer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn send_url_scheme(url: String) -> ResultType<()> {
|
||||
connect(1_000, "_url")
|
||||
.await?
|
||||
.send(&Data::UrlLink(url))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn verify_ffi_enum_data_size() {
|
||||
println!("{}", std::mem::size_of::<Data>());
|
||||
assert!(std::mem::size_of::<Data>() < 96);
|
||||
}
|
||||
}
|
||||
|
||||
399
src/keyboard.rs
399
src/keyboard.rs
@@ -2,11 +2,12 @@
|
||||
use crate::client::get_key_state;
|
||||
use crate::common::GrabState;
|
||||
#[cfg(feature = "flutter")]
|
||||
use crate::flutter::FlutterHandler;
|
||||
use crate::flutter::{CUR_SESSION_ID, SESSIONS};
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
use crate::ui::remote::SciterHandler;
|
||||
use crate::ui_session_interface::Session;
|
||||
use hbb_common::{log, message_proto::*};
|
||||
use crate::ui::CUR_SESSION;
|
||||
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};
|
||||
@@ -19,19 +20,16 @@ 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);
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
lazy_static::lazy_static! {
|
||||
static ref CUR_SESSION: Arc<Mutex<Option<Session<FlutterHandler>>>> = Default::default();
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref CUR_SESSION: Arc<Mutex<Option<Session<SciterHandler>>>> = Default::default();
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref TO_RELEASE: Arc<Mutex<HashSet<Key>>> = Arc::new(Mutex::new(HashSet::<Key>::new()));
|
||||
static ref MODIFIERS_STATE: Mutex<HashMap<Key, bool>> = {
|
||||
@@ -48,24 +46,22 @@ lazy_static::lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
pub fn set_cur_session(session: Session<FlutterHandler>) {
|
||||
*CUR_SESSION.lock().unwrap() = Some(session);
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
pub fn set_cur_session(session: Session<SciterHandler>) {
|
||||
*CUR_SESSION.lock().unwrap() = Some(session);
|
||||
}
|
||||
|
||||
pub mod client {
|
||||
use super::*;
|
||||
|
||||
pub fn get_keyboard_mode() -> String {
|
||||
#[cfg(not(feature = "cli"))]
|
||||
if let Some(handler) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||
return handler.get_keyboard_mode();
|
||||
}
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||
return session.get_keyboard_mode();
|
||||
}
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(session) = SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&*CUR_SESSION_ID.read().unwrap())
|
||||
{
|
||||
return session.get_keyboard_mode();
|
||||
}
|
||||
"legacy".to_string()
|
||||
}
|
||||
|
||||
@@ -77,6 +73,8 @@ pub mod client {
|
||||
match state {
|
||||
GrabState::Ready => {}
|
||||
GrabState::Run => {
|
||||
#[cfg(windows)]
|
||||
update_grab_get_key_name();
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
KEYBOARD_HOOKED.swap(true, Ordering::SeqCst);
|
||||
|
||||
@@ -99,13 +97,17 @@ pub mod client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_event(event: &Event) {
|
||||
pub fn process_event(event: &Event, lock_modes: Option<i32>) -> KeyboardMode {
|
||||
let keyboard_mode = get_keyboard_mode_enum();
|
||||
|
||||
if is_long_press(&event) {
|
||||
return;
|
||||
return keyboard_mode;
|
||||
}
|
||||
if let Some(key_event) = event_to_key_event(&event) {
|
||||
|
||||
for key_event in event_to_key_events(&event, keyboard_mode, lock_modes) {
|
||||
send_key_event(&key_event);
|
||||
}
|
||||
keyboard_mode
|
||||
}
|
||||
|
||||
pub fn get_modifiers_state(
|
||||
@@ -164,15 +166,20 @@ pub mod client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lock_screen() {
|
||||
pub fn event_lock_screen() -> KeyEvent {
|
||||
let mut key_event = KeyEvent::new();
|
||||
key_event.set_control_key(ControlKey::LockScreen);
|
||||
key_event.down = true;
|
||||
key_event.mode = KeyboardMode::Legacy.into();
|
||||
send_key_event(&key_event);
|
||||
key_event
|
||||
}
|
||||
|
||||
pub fn ctrl_alt_del() {
|
||||
#[inline]
|
||||
pub fn lock_screen() {
|
||||
send_key_event(&event_lock_screen());
|
||||
}
|
||||
|
||||
pub fn event_ctrl_alt_del() -> KeyEvent {
|
||||
let mut key_event = KeyEvent::new();
|
||||
if get_peer_platform() == "Windows" {
|
||||
key_event.set_control_key(ControlKey::CtrlAltDel);
|
||||
@@ -183,10 +190,30 @@ pub mod client {
|
||||
key_event.press = true;
|
||||
}
|
||||
key_event.mode = KeyboardMode::Legacy.into();
|
||||
send_key_event(&key_event);
|
||||
key_event
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ctrl_alt_del() {
|
||||
send_key_event(&event_ctrl_alt_del());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn update_grab_get_key_name() {
|
||||
match get_keyboard_mode_enum() {
|
||||
KeyboardMode::Map => rdev::set_get_key_unicode(false),
|
||||
KeyboardMode::Translate => rdev::set_get_key_unicode(true),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
#[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 || {
|
||||
@@ -195,22 +222,61 @@ pub fn start_grab_loop() {
|
||||
if key == Key::CapsLock || key == Key::NumLock {
|
||||
return Some(event);
|
||||
}
|
||||
if KEYBOARD_HOOKED.load(Ordering::SeqCst) {
|
||||
client::process_event(&event);
|
||||
|
||||
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 {
|
||||
return None;
|
||||
None
|
||||
} else {
|
||||
return Some(event);
|
||||
Some(event)
|
||||
}
|
||||
} else {
|
||||
return Some(event);
|
||||
Some(event)
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
match _scan_code {
|
||||
0x1D | 0x021D => rdev::set_modifier(Key::ControlLeft, is_press),
|
||||
0xE01D => rdev::set_modifier(Key::ControlRight, is_press),
|
||||
0x2A => rdev::set_modifier(Key::ShiftLeft, is_press),
|
||||
0x36 => rdev::set_modifier(Key::ShiftRight, is_press),
|
||||
0x38 => rdev::set_modifier(Key::Alt, is_press),
|
||||
// Right Alt
|
||||
0xE038 => rdev::set_modifier(Key::AltGr, is_press),
|
||||
0xE05B => rdev::set_modifier(Key::MetaLeft, is_press),
|
||||
0xE05C => rdev::set_modifier(Key::MetaRight, is_press),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
// AltGr
|
||||
if _scan_code == 0x021D {
|
||||
IS_0X021D_DOWN = is_press;
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
EventType::KeyPress(key) => try_handle_keyboard(event, key, true),
|
||||
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)
|
||||
}
|
||||
@@ -222,7 +288,7 @@ pub fn start_grab_loop() {
|
||||
if let Key::Unknown(keycode) = key {
|
||||
log::error!("rdev get unknown key, keycode is : {:?}", keycode);
|
||||
} else {
|
||||
client::process_event(&event);
|
||||
client::process_event(&event, None);
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -254,7 +320,9 @@ pub fn release_remote_keys() {
|
||||
for key in to_release {
|
||||
let event_type = EventType::KeyRelease(key);
|
||||
let event = event_type_to_event(event_type);
|
||||
client::process_event(&event);
|
||||
// to-do: BUG
|
||||
// Release events should be sent to the corresponding sessions, instead of current session.
|
||||
client::process_event(&event, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +335,23 @@ pub fn get_keyboard_mode_enum() -> KeyboardMode {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn add_numlock_capslock_status(key_event: &mut KeyEvent) {
|
||||
fn add_numlock_capslock_with_lock_modes(key_event: &mut KeyEvent, lock_modes: i32) {
|
||||
const CAPS_LOCK: i32 = 1;
|
||||
const NUM_LOCK: i32 = 2;
|
||||
// const SCROLL_LOCK: i32 = 3;
|
||||
if lock_modes & (1 << CAPS_LOCK) != 0 {
|
||||
key_event.modifiers.push(ControlKey::CapsLock.into());
|
||||
}
|
||||
if lock_modes & (1 << NUM_LOCK) != 0 {
|
||||
key_event.modifiers.push(ControlKey::NumLock.into());
|
||||
}
|
||||
// if lock_modes & (1 << SCROLL_LOCK) != 0 {
|
||||
// key_event.modifiers.push(ControlKey::ScrollLock.into());
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn add_numlock_capslock_status(key_event: &mut KeyEvent) {
|
||||
if get_key_state(enigo::Key::CapsLock) {
|
||||
key_event.modifiers.push(ControlKey::CapsLock.into());
|
||||
}
|
||||
@@ -315,7 +399,11 @@ fn update_modifiers_state(event: &Event) {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn event_to_key_event(event: &Event) -> Option<KeyEvent> {
|
||||
pub fn event_to_key_events(
|
||||
event: &Event,
|
||||
keyboard_mode: KeyboardMode,
|
||||
lock_modes: Option<i32>,
|
||||
) -> Vec<KeyEvent> {
|
||||
let mut key_event = KeyEvent::new();
|
||||
update_modifiers_state(event);
|
||||
|
||||
@@ -329,61 +417,91 @@ pub fn event_to_key_event(event: &Event) -> Option<KeyEvent> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let keyboard_mode = get_keyboard_mode_enum();
|
||||
let mut peer = get_peer_platform().to_lowercase();
|
||||
peer.retain(|c| !c.is_whitespace());
|
||||
|
||||
key_event.mode = keyboard_mode.into();
|
||||
let mut key_event = match keyboard_mode {
|
||||
KeyboardMode::Map => map_keyboard_mode(event, key_event)?,
|
||||
KeyboardMode::Translate => translate_keyboard_mode(event, key_event)?,
|
||||
let mut key_events = match keyboard_mode {
|
||||
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(peer.as_str(), event, key_event),
|
||||
_ => {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
legacy_keyboard_mode(event, key_event)?
|
||||
legacy_keyboard_mode(event, key_event)
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
None?
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
add_numlock_capslock_status(&mut key_event);
|
||||
|
||||
return Some(key_event);
|
||||
if keyboard_mode != KeyboardMode::Translate {
|
||||
for key_event in &mut key_events {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Some(lock_modes) = lock_modes {
|
||||
add_numlock_capslock_with_lock_modes(key_event, lock_modes);
|
||||
} else {
|
||||
add_numlock_capslock_status(key_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
key_events
|
||||
}
|
||||
|
||||
pub fn event_type_to_event(event_type: EventType) -> Event {
|
||||
Event {
|
||||
event_type,
|
||||
time: SystemTime::now(),
|
||||
name: None,
|
||||
unicode: None,
|
||||
code: 0,
|
||||
scan_code: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_key_event(key_event: &KeyEvent) {
|
||||
#[cfg(not(feature = "cli"))]
|
||||
if let Some(handler) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||
handler.send_key_event(key_event);
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||
session.send_key_event(key_event);
|
||||
}
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(session) = SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&*CUR_SESSION_ID.read().unwrap())
|
||||
{
|
||||
session.send_key_event(key_event);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_peer_platform() -> String {
|
||||
#[cfg(not(feature = "cli"))]
|
||||
if let Some(handler) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||
return handler.peer_platform();
|
||||
}
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||
return session.peer_platform();
|
||||
}
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(session) = SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&*CUR_SESSION_ID.read().unwrap())
|
||||
{
|
||||
return session.peer_platform();
|
||||
}
|
||||
"Windows".to_string()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<KeyEvent> {
|
||||
pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec<KeyEvent> {
|
||||
let mut events = Vec::new();
|
||||
// legacy mode(0): Generate characters locally, look for keycode on other side.
|
||||
let (mut key, down_or_up) = match event.event_type {
|
||||
EventType::KeyPress(key) => (key, true),
|
||||
EventType::KeyRelease(key) => (key, false),
|
||||
_ => {
|
||||
return None;
|
||||
return events;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -429,7 +547,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<Ke
|
||||
unsafe {
|
||||
IS_ALT_GR = true;
|
||||
}
|
||||
return None;
|
||||
return events;
|
||||
}
|
||||
Some(ControlKey::Control)
|
||||
}
|
||||
@@ -461,7 +579,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<Ke
|
||||
Key::Delete => {
|
||||
if is_win && ctrl && alt {
|
||||
client::ctrl_alt_del();
|
||||
return None;
|
||||
return events;
|
||||
}
|
||||
Some(ControlKey::Delete)
|
||||
}
|
||||
@@ -499,7 +617,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<Ke
|
||||
Key::KpMinus => Some(ControlKey::Subtract),
|
||||
Key::KpPlus => Some(ControlKey::Add),
|
||||
Key::CapsLock | Key::NumLock | Key::ScrollLock => {
|
||||
return None;
|
||||
return events;
|
||||
}
|
||||
Key::Home => Some(ControlKey::Home),
|
||||
Key::End => Some(ControlKey::End),
|
||||
@@ -512,7 +630,11 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<Ke
|
||||
if let Some(k) = control_key {
|
||||
key_event.set_control_key(k);
|
||||
} else {
|
||||
let mut chr = match event.name {
|
||||
let name = event
|
||||
.unicode
|
||||
.as_ref()
|
||||
.and_then(|unicode| unicode.name.clone());
|
||||
let mut chr = match &name {
|
||||
Some(ref s) => {
|
||||
if s.len() <= 2 {
|
||||
// exclude chinese characters
|
||||
@@ -582,12 +704,12 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<Ke
|
||||
if chr != '\0' {
|
||||
if chr == 'l' && is_win && command {
|
||||
client::lock_screen();
|
||||
return None;
|
||||
return events;
|
||||
}
|
||||
key_event.set_chr(chr as _);
|
||||
} else {
|
||||
log::error!("Unknown key {:?}", &event);
|
||||
return None;
|
||||
return events;
|
||||
}
|
||||
}
|
||||
let (alt, ctrl, shift, command) = client::get_modifiers_state(alt, ctrl, shift, command);
|
||||
@@ -596,10 +718,11 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<Ke
|
||||
if down_or_up == true {
|
||||
key_event.down = true;
|
||||
}
|
||||
Some(key_event)
|
||||
events.push(key_event);
|
||||
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;
|
||||
@@ -610,13 +733,17 @@ 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" => event.scan_code,
|
||||
"macos" => {
|
||||
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 {
|
||||
return None;
|
||||
}
|
||||
event.scan_code
|
||||
}
|
||||
OS_LOWER_MACOS => {
|
||||
if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" {
|
||||
rdev::win_scancode_to_macos_iso_code(event.scan_code)?
|
||||
} else {
|
||||
@@ -626,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 {
|
||||
@@ -650,6 +777,108 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<KeyEv
|
||||
Some(key_event)
|
||||
}
|
||||
|
||||
pub fn translate_keyboard_mode(_event: &Event, mut _key_event: KeyEvent) -> Option<KeyEvent> {
|
||||
None
|
||||
fn try_fill_unicode(event: &Event, key_event: &KeyEvent, events: &mut Vec<KeyEvent>) {
|
||||
match &event.unicode {
|
||||
Some(unicode_info) => {
|
||||
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 => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn is_hot_key_modifiers_down() -> bool {
|
||||
if rdev::get_modifier(Key::ControlLeft) || rdev::get_modifier(Key::ControlRight) {
|
||||
return true;
|
||||
}
|
||||
if rdev::get_modifier(Key::Alt) || rdev::get_modifier(Key::AltGr) {
|
||||
return true;
|
||||
}
|
||||
if rdev::get_modifier(Key::MetaLeft) || rdev::get_modifier(Key::MetaRight) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
|
||||
#[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 {
|
||||
return events;
|
||||
}
|
||||
|
||||
if IS_0X021D_DOWN {
|
||||
if event.scan_code == 0xE038 {
|
||||
return events;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if unsafe { IS_0X021D_DOWN } || !is_hot_key_modifiers_down() {
|
||||
try_fill_unicode(event, &key_event, &mut events);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
if IS_0X021D_DOWN {
|
||||
return events;
|
||||
}
|
||||
}
|
||||
|
||||
#[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_key_code(peer, event, key_event) {
|
||||
events.push(evt);
|
||||
}
|
||||
}
|
||||
events
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ mod id;
|
||||
mod it;
|
||||
mod ja;
|
||||
mod ko;
|
||||
mod nl;
|
||||
mod pl;
|
||||
mod ptbr;
|
||||
mod ro;
|
||||
mod ru;
|
||||
mod sk;
|
||||
mod tr;
|
||||
@@ -39,6 +41,7 @@ lazy_static::lazy_static! {
|
||||
("it", "Italiano"),
|
||||
("fr", "Français"),
|
||||
("de", "Deutsch"),
|
||||
("nl", "Nederlands"),
|
||||
("cn", "简体中文"),
|
||||
("tw", "繁體中文"),
|
||||
("pt", "Português"),
|
||||
@@ -65,6 +68,7 @@ lazy_static::lazy_static! {
|
||||
("sr", "Srpski"),
|
||||
("th", "ภาษาไทย"),
|
||||
("sl", "Slovenščina"),
|
||||
("ro", "Română"),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -79,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() {
|
||||
@@ -97,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(),
|
||||
@@ -123,6 +128,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
|
||||
"sr" => sr::T.deref(),
|
||||
"th" => th::T.deref(),
|
||||
"sl" => sl::T.deref(),
|
||||
"ro" => ro::T.deref(),
|
||||
_ => en::T.deref(),
|
||||
};
|
||||
if let Some(v) = m.get(&name as &str) {
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Silenciar"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Entrada d'àudio"),
|
||||
("Enhancements", "Millores"),
|
||||
("Hardware Codec", "Còdec de hardware"),
|
||||
@@ -51,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"),
|
||||
@@ -206,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ó"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Sortir"),
|
||||
("Tags", ""),
|
||||
("Search ID", "Cerca ID"),
|
||||
("Current Wayland display server is not supported", "El servidor de visualització actual de Wayland no és compatible"),
|
||||
("whitelist_sep", ""),
|
||||
("Add ID", "Afegir ID"),
|
||||
("Add Tag", "Afegir tag"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"),
|
||||
("Ignore Battery Optimizations", "Ignorar optimizacions de la bateria"),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Connexió no disponible"),
|
||||
("Legacy mode", "Mode heretat"),
|
||||
("Map mode", "Mode mapa"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
180
src/lang/cn.rs
180
src/lang/cn.rs
@@ -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,42 +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", "主页"),
|
||||
("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", "跳过"),
|
||||
@@ -65,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", "成功"),
|
||||
@@ -95,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", "设置画面质量"),
|
||||
@@ -115,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", "Key 不匹配"),
|
||||
("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", "安装路径"),
|
||||
@@ -154,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", "动作"),
|
||||
@@ -166,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", "接受"),
|
||||
@@ -176,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", "修复"),
|
||||
@@ -205,30 +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"),
|
||||
("Current Wayland display server is not supported", "不支持 Wayland 显示服务器"),
|
||||
("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", "从收藏中删除"),
|
||||
@@ -238,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", "触屏模式"),
|
||||
@@ -278,16 +284,16 @@ 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", "开启录屏权限将自动开启服务,允许其他设备向此设备请求建立连接。"),
|
||||
("android_stop_service_tip", "关闭服务将自动关闭所有已建立的连接。"),
|
||||
("android_version_audio_tip", "当前安卓版本不支持音频录制,请升级至安卓10或更高。"),
|
||||
("android_version_audio_tip", "当前安卓版本不支持音频录制,请升级至安卓 10 或更高。"),
|
||||
("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", "帮助"),
|
||||
@@ -306,9 +312,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "保持 RustDesk 后台服务"),
|
||||
("Ignore Battery Optimizations", "忽略电池优化"),
|
||||
("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的 RustDesk 应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"),
|
||||
("Start on Boot", "开机自启动"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "开机自动启动屏幕共享服务,此功能需要一些特殊权限。"),
|
||||
("Connection not allowed", "对方不允许连接"),
|
||||
("Legacy mode", "传统模式"),
|
||||
("Map mode", "1:1传输"),
|
||||
("Map mode", "1:1 传输"),
|
||||
("Translate mode", "翻译模式"),
|
||||
("Use permanent password", "使用固定密码"),
|
||||
("Use both passwords", "同时使用两种密码"),
|
||||
@@ -349,16 +357,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", "录屏"),
|
||||
@@ -373,7 +381,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", "其他"),
|
||||
@@ -390,7 +398,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", "允许密码或点击访问"),
|
||||
@@ -401,17 +409,55 @@ 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_experiment_tip", "Wayland 支持处于实验阶段,如果你需要使用无人值守访问,请使用 X11。"),
|
||||
("Right click to select tabs", "右键选择选项卡"),
|
||||
("Skipped", "已跳过"),
|
||||
("Add to Address Book", "添加到地址簿"),
|
||||
("Group", "小组"),
|
||||
("Search", "搜索"),
|
||||
("Closed manually by the web console", "被web控制台手动关闭"),
|
||||
("Closed manually by web console", "被 web 控制台手动关闭"),
|
||||
("Local keyboard type", "本地键盘类型"),
|
||||
("Select local keyboard type", "请选择本地键盘类型"),
|
||||
("software_render_tip", "如果你使用英伟达显卡, 并且远程窗口在会话建立后会立刻关闭, 那么安装nouveau驱动并且选择使用软件渲染可能会有帮助。重启软件后生效。"),
|
||||
("Always use software rendering", "使用软件渲染"),
|
||||
("software_render_tip", "如果你使用英伟达显卡, 并且远程窗口在会话建立后会立刻关闭, 那么安装 nouveau 驱动并且选择使用软件渲染可能会有帮助。重启软件后生效。"),
|
||||
("Always use software rendering", "始终使用软件渲染"),
|
||||
("config_input", "为了能够通过键盘控制远程桌面, 请给予 RustDesk \"输入监控\" 权限。"),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", "为了支持通过麦克风进行音频传输,请给予 RustDesk \"录音\"权限。"),
|
||||
("request_elevation_tip", "如果对面有人, 也可以请求提升权限。"),
|
||||
("Wait", "等待"),
|
||||
("Elevation Error", "提权失败"),
|
||||
("Ask the remote user for authentication", "请求远端用户授权"),
|
||||
("Choose this if the remote account is administrator", "当对面电脑是管理员账号时选择该选项"),
|
||||
("Transmit the username and password of administrator", "发送管理员账号的用户名密码"),
|
||||
("still_click_uac_tip", "依然需要被控端用户在运行 RustDesk 的 UAC 窗口点击确认。"),
|
||||
("Request Elevation", "请求提权"),
|
||||
("wait_accept_uac_tip", "请等待远端用户确认 UAC 对话框。"),
|
||||
("Elevate successfully", "提权成功"),
|
||||
("uppercase", "大写字母"),
|
||||
("lowercase", "小写字母"),
|
||||
("digit", "数字"),
|
||||
("special character", "特殊字符"),
|
||||
("length>=8", "长度不小于 8"),
|
||||
("Weak", "弱"),
|
||||
("Medium", "中"),
|
||||
("Strong", "强"),
|
||||
("Switch Sides", "反转访问方向"),
|
||||
("Please confirm if you want to share your desktop?", "请确认是否要让对方访问你的桌面?"),
|
||||
("Display", "显示"),
|
||||
("Default View Style", "默认显示方式"),
|
||||
("Default Scroll Style", "默认滚动方式"),
|
||||
("Default Image Quality", "默认图像质量"),
|
||||
("Default Codec", "默认编解码"),
|
||||
("Bitrate", "码率"),
|
||||
("FPS", "帧率"),
|
||||
("Auto", "自动"),
|
||||
("Other Default Options", "其它默认选项"),
|
||||
("Voice call", "语音通话"),
|
||||
("Text chat", "文字聊天"),
|
||||
("Stop voice call", "停止语音通话"),
|
||||
("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r,或者在卡片选项里选择强制走中继连接。"),
|
||||
("Reconnect", "重连"),
|
||||
("Codec", "编解码"),
|
||||
("Resolution", "分辨率"),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Ztlumit"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Vstup zvuku"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Odhlásit se"),
|
||||
("Tags", "Štítky"),
|
||||
("Search ID", "Hledat identifikátor"),
|
||||
("Current Wayland display server is not supported", "Zobrazovací server Wayland zatím není podporován"),
|
||||
("whitelist_sep", "Odělováno čárkou, středníkem, mezerou nebo koncem řádku"),
|
||||
("Add ID", "Přidat identifikátor"),
|
||||
("Add Tag", "Přidat štítek"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", ""),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Sluk for mikrofonen"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Lydindgang"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "logger af"),
|
||||
("Tags", "Nøgleord"),
|
||||
("Search ID", "Søg ID"),
|
||||
("Current Wayland display server is not supported", "Den aktuelle Wayland-Anzege-server understøttes ikke"),
|
||||
("whitelist_sep", "Adskilt af komma, semikolon, rum eller linjepaus"),
|
||||
("Add ID", "Tilføj ID"),
|
||||
("Add Tag", "Tilføj nøgleord"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"),
|
||||
("Ignore Battery Optimizations", "Ignorer betteri optimeringer"),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Forbindelse ikke tilladt"),
|
||||
("Legacy mode", "Bagudkompatibilitetstilstand"),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
124
src/lang/de.rs
124
src/lang/de.rs
@@ -13,7 +13,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Service is running", "Vermittlungsdienst aktiv"),
|
||||
("Service is not running", "Vermittlungsdienst deaktiviert"),
|
||||
("not_ready_status", "Nicht bereit. Bitte überprüfen Sie Ihre Netzwerkverbindung."),
|
||||
("Control Remote Desktop", "Entfernten PC steuern"),
|
||||
("Control Remote Desktop", "Entfernten Desktop steuern"),
|
||||
("Transfer File", "Datei übertragen"),
|
||||
("Connect", "Verbinden"),
|
||||
("Recent Sessions", "Letzte Sitzungen"),
|
||||
@@ -26,9 +26,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable Keyboard/Mouse", "Tastatur und Maus aktivieren"),
|
||||
("Enable Clipboard", "Zwischenablage aktivieren"),
|
||||
("Enable File Transfer", "Dateiübertragung aktivieren"),
|
||||
("Enable TCP Tunneling", "TCP-Tunnel aktivieren"),
|
||||
("Enable TCP Tunneling", "TCP-Tunnelung aktivieren"),
|
||||
("IP Whitelisting", "IP-Whitelist"),
|
||||
("ID/Relay Server", "ID/Vermittlungsserver"),
|
||||
("ID/Relay Server", "ID/Relay-Server"),
|
||||
("Import Server Config", "Serverkonfiguration importieren"),
|
||||
("Export Server Config", "Serverkonfiguration exportieren"),
|
||||
("Import server configuration successfully", "Serverkonfiguration erfolgreich importiert"),
|
||||
@@ -37,21 +37,28 @@ 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"),
|
||||
("Slogan_tip", "Mit Herzblut programmiert - in einer Welt, die im Chaos versinkt!"),
|
||||
("Privacy Statement", "Datenschutz"),
|
||||
("Mute", "Stummschalten"),
|
||||
("Build Date", "Erstelldatum"),
|
||||
("Version", "Version"),
|
||||
("Home", "Startseite"),
|
||||
("Audio Input", "Audioeingang"),
|
||||
("Enhancements", "Verbesserungen"),
|
||||
("Hardware Codec", "Hardware-Codec"),
|
||||
("Adaptive Bitrate", "Bitrate automatisch anpassen"),
|
||||
("ID Server", "ID-Server"),
|
||||
("Relay Server", "Vermittlungsserver"),
|
||||
("Relay Server", "Relay-Server"),
|
||||
("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"),
|
||||
@@ -127,15 +134,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Insert Lock", "Win+L (Sperren) senden"),
|
||||
("Refresh", "Aktualisieren"),
|
||||
("ID does not exist", "Diese ID existiert nicht."),
|
||||
("Failed to connect to rendezvous server", "Verbindung zum Vermittlungsserver fehlgeschlagen"),
|
||||
("Failed to connect to rendezvous server", "Verbindung zum Rendezvous-Server fehlgeschlagen"),
|
||||
("Please try later", "Bitte versuchen Sie es später erneut."),
|
||||
("Remote desktop is offline", "Entfernter PC ist offline."),
|
||||
("Remote desktop is offline", "Entfernter Desktop ist offline."),
|
||||
("Key mismatch", "Schlüssel stimmen nicht überein."),
|
||||
("Timeout", "Zeitüberschreitung"),
|
||||
("Failed to connect to relay server", "Verbindung zum Vermittlungsserver fehlgeschlagen"),
|
||||
("Failed to connect via rendezvous server", "Verbindung über Vermittlungsserver ist fehlgeschlagen"),
|
||||
("Failed to connect to relay server", "Verbindung zum Relay-Server ist fehlgeschlagen"),
|
||||
("Failed to connect via rendezvous server", "Verbindung über Rendezvous-Server ist fehlgeschlagen"),
|
||||
("Failed to connect via relay server", "Verbindung über Relay-Server ist fehlgeschlagen"),
|
||||
("Failed to make direct connection to remote desktop", "Direkte Verbindung zum entfernten PC fehlgeschlagen"),
|
||||
("Failed to make direct connection to remote desktop", "Direkte Verbindung zum entfernten Desktop ist fehlgeschlagen"),
|
||||
("Set Password", "Passwort festlegen"),
|
||||
("OS Password", "Betriebssystem-Passwort"),
|
||||
("install_tip", "Aufgrund der Benutzerkontensteuerung (UAC) kann RustDesk in manchen Fällen nicht ordnungsgemäß funktionieren. Um die Benutzerkontensteuerung zu umgehen, klicken Sie bitte auf die Schaltfläche unten und installieren RustDesk auf dem System."),
|
||||
@@ -145,7 +152,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Configure", "Konfigurieren"),
|
||||
("config_acc", "Um Ihren PC aus der Ferne zu steuern, müssen Sie RustDesk Zugriffsrechte erteilen."),
|
||||
("config_screen", "Um aus der Ferne auf Ihren PC zugreifen zu können, müssen Sie RustDesk die Berechtigung \"Bildschirmaufnahme\" erteilen."),
|
||||
("Installing ...", "Installiere..."),
|
||||
("Installing ...", "Installieren..."),
|
||||
("Install", "Installieren"),
|
||||
("Installation", "Installation"),
|
||||
("Installation Path", "Installationspfad"),
|
||||
@@ -197,34 +204,33 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Warning", "Warnung"),
|
||||
("Login screen using Wayland is not supported", "Anmeldebildschirm mit Wayland wird nicht unterstützt."),
|
||||
("Reboot required", "Neustart erforderlich"),
|
||||
("Unsupported display server ", "Nicht unterstützter Display-Server"),
|
||||
("Unsupported display server ", "Nicht unterstützter Anzeigeserver"),
|
||||
("x11 expected", "X11 erwartet"),
|
||||
("Port", "Port"),
|
||||
("Settings", "Einstellungen"),
|
||||
("Username", " Benutzername"),
|
||||
("Username", "Benutzername"),
|
||||
("Invalid port", "Ungültiger Port"),
|
||||
("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"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Verify", "Überprüfen"),
|
||||
("Remember me", "Login speichern"),
|
||||
("Trust this device", "Diesem Gerät vertrauen"),
|
||||
("Verification code", "Verifizierungscode"),
|
||||
("verification_tip", "Es wurde ein neues Gerät erkannt und ein Verifizierungscode an die registrierte E-Mail-Adresse gesendet. Geben Sie den Verifizierungscode ein, um sich weiter anzumelden."),
|
||||
("Logout", "Abmelden"),
|
||||
("Tags", "Schlagworte"),
|
||||
("Search ID", "Suche ID"),
|
||||
("Current Wayland display server is not supported", "Der aktuelle Wayland-Anzeigeserver wird nicht unterstützt."),
|
||||
("whitelist_sep", "Getrennt durch Komma, Semikolon, Leerzeichen oder Zeilenumbruch"),
|
||||
("Add ID", "ID hinzufügen"),
|
||||
("Add Tag", "Stichwort hinzufügen"),
|
||||
("Unselect all tags", "Alle Stichworte abwählen"),
|
||||
("Network error", "Netzwerkfehler"),
|
||||
("Username missed", "Benutzername vergessen"),
|
||||
("Username missed", "Benutzernamen vergessen"),
|
||||
("Password missed", "Passwort vergessen"),
|
||||
("Wrong credentials", "Falsche Anmeldedaten"),
|
||||
("Edit Tag", "Schlagwort bearbeiten"),
|
||||
@@ -241,7 +247,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Remote ID", "Entfernte ID"),
|
||||
("Paste", "Einfügen"),
|
||||
("Paste here?", "Hier einfügen?"),
|
||||
("Are you sure to close the connection?", "Möchten Sie diese Verbindung wirklich trennen?"),
|
||||
("Are you sure to close the connection?", "Möchten Sie diese Verbindung wirklich schließen?"),
|
||||
("Download new version", "Neue Version herunterladen"),
|
||||
("Touch mode", "Touch-Modus"),
|
||||
("Mouse mode", "Mausmodus"),
|
||||
@@ -264,27 +270,27 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Note", "Hinweis"),
|
||||
("Connection", "Verbindung"),
|
||||
("Share Screen", "Bildschirm freigeben"),
|
||||
("CLOSE", "DEAKTIV."),
|
||||
("OPEN", "AKTIVIER."),
|
||||
("CLOSE", "SCHLIEẞEN"),
|
||||
("OPEN", "ÖFFNEN"),
|
||||
("Chat", "Chat"),
|
||||
("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?"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"),
|
||||
("Ignore Battery Optimizations", "Akkuoptimierung ignorieren"),
|
||||
("android_open_battery_optimizations_tip", "Möchten Sie die Einstellungen zur Akkuoptimierung öffnen?"),
|
||||
("Start on Boot", "Beim Booten Starten"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Bildschirmfreigabedienst beim Booten starten, benötigt zusätzliche Berechtigungen"),
|
||||
("Connection not allowed", "Verbindung abgelehnt"),
|
||||
("Legacy mode", "Kompatibilitätsmodus"),
|
||||
("Map mode", "Kartenmodus"),
|
||||
@@ -325,7 +333,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Mobile Actions", "Mobile Aktionen"),
|
||||
("Select Monitor", "Bildschirm auswählen"),
|
||||
("Control Actions", "Aktionen"),
|
||||
("Display Settings", "Bildschirmeinstellungen"),
|
||||
("Display Settings", "Anzeigeeinstellungen"),
|
||||
("Ratio", "Verhältnis"),
|
||||
("Image Quality", "Bildqualität"),
|
||||
("Scroll Style", "Scroll-Stil"),
|
||||
@@ -336,7 +344,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Secure Connection", "Sichere Verbindung"),
|
||||
("Insecure Connection", "Unsichere Verbindung"),
|
||||
("Scale original", "Keine Skalierung"),
|
||||
("Scale adaptive", "Automatische Skalierung"),
|
||||
("Scale adaptive", "Anpassbare Skalierung"),
|
||||
("General", "Allgemein"),
|
||||
("Security", "Sicherheit"),
|
||||
("Theme", "Farbgebung"),
|
||||
@@ -356,7 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clear", "Zurücksetzen"),
|
||||
("Audio Input Device", "Audioeingabegerät"),
|
||||
("Deny remote access", "Fernzugriff verbieten"),
|
||||
("Use IP Whitelisting", "IP-Whitelist benutzen"),
|
||||
("Use IP Whitelisting", "IP-Whitelist verwenden"),
|
||||
("Network", "Netzwerk"),
|
||||
("Enable RDP", "RDP aktivieren"),
|
||||
("Pin menubar", "Menüleiste anpinnen"),
|
||||
@@ -384,13 +392,13 @@ 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 Bildschirm aus, der freigegeben werden soll (auf der Peer-Seite arbeiten)."),
|
||||
("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"),
|
||||
("Continue with", "Fortfahren mit"),
|
||||
("Elevate", "Erheben"),
|
||||
("Zoom cursor", "Cursor zoomen"),
|
||||
("Zoom cursor", "Cursor vergrößern"),
|
||||
("Accept sessions via password", "Sitzung mit Passwort bestätigen"),
|
||||
("Accept sessions via click", "Sitzung mit einem Klick bestätigen"),
|
||||
("Accept sessions via both", "Sitzung mit Klick und Passwort bestätigen"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", "Zum Adressbuch hinzufügen"),
|
||||
("Group", "Gruppe"),
|
||||
("Search", "Suchen"),
|
||||
("Closed manually by the web console", "Manuell über die Webkonsole beendet"),
|
||||
("Closed manually by web console", "Manuell über die Webkonsole geschlossen"),
|
||||
("Local keyboard type", "Lokaler Tastaturtyp"),
|
||||
("Select local keyboard type", "Lokalen Tastaturtyp auswählen"),
|
||||
("software_render_tip", "Wenn Sie eine Nvidia-Grafikkarte haben und sich das entfernte Fenster sofort nach dem Herstellen der Verbindung schließt, kann es helfen, den Nouveau-Treiber zu installieren und Software-Rendering zu verwenden. Ein Neustart der Software ist erforderlich."),
|
||||
("Always use software rendering", "Software-Rendering immer verwenden"),
|
||||
("config_input", "Um den entfernten Desktop mit der Tastatur steuern zu können, müssen Sie RustDesk \"Input Monitoring\"-Rechte erteilen."),
|
||||
].iter().cloned().collect();
|
||||
("config_input", "Um den entfernten Desktop mit der Tastatur steuern zu können, müssen Sie RustDesk die Berechtigung \"Input Monitoring\" erteilen."),
|
||||
("config_microphone", "Um aus der Ferne sprechen zu können, müssen Sie RustDesk die Berechtigung \"Audio aufzeichnen\" erteilen."),
|
||||
("request_elevation_tip", "Sie können auch erhöhte Rechte anfordern, wenn sich jemand auf der Gegenseite befindet."),
|
||||
("Wait", "Warten"),
|
||||
("Elevation Error", "Berechtigungsfehler"),
|
||||
("Ask the remote user for authentication", "Den entfernten Benutzer zur Authentifizierung auffordern"),
|
||||
("Choose this if the remote account is administrator", "Wählen Sie dies, wenn das entfernte Konto Administrator ist"),
|
||||
("Transmit the username and password of administrator", "Übermitteln Sie den Benutzernamen und das Passwort des Administrators"),
|
||||
("still_click_uac_tip", "Der entfernte Benutzer muss immer noch im UAC-Fenster von RustDesk auf OK klicken."),
|
||||
("Request Elevation", "Erhöhte Rechte anfordern"),
|
||||
("wait_accept_uac_tip", "Bitte warten Sie, bis der entfernte Benutzer den UAC-Dialog akzeptiert hat."),
|
||||
("Elevate successfully", "Erhöhung der Rechte erfolgreich"),
|
||||
("uppercase", "Großbuchstaben"),
|
||||
("lowercase", "Kleinbuchstaben"),
|
||||
("digit", "Ziffern"),
|
||||
("special character", "Sonderzeichen"),
|
||||
("length>=8", "Länge ≥ 8"),
|
||||
("Weak", "Schwach"),
|
||||
("Medium", "Mittel"),
|
||||
("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."),
|
||||
("Display", "Anzeige"),
|
||||
("Default View Style", "Standard-Ansichtsstil"),
|
||||
("Default Scroll Style", "Standard-Scroll-Stil"),
|
||||
("Default Image Quality", "Standard-Bildqualität"),
|
||||
("Default Codec", "Standard-Codec"),
|
||||
("Bitrate", "Bitrate"),
|
||||
("FPS", "fps"),
|
||||
("Auto", "Automatisch"),
|
||||
("Other Default Options", "Weitere Standardeinstellungen"),
|
||||
("Voice call", "Sprachanruf"),
|
||||
("Text chat", "Text-Chat"),
|
||||
("Stop voice call", "Sprachanruf beenden"),
|
||||
("relay_hint_tip", "Wenn eine direkte Verbindung nicht möglich ist, können Sie versuchen, eine Verbindung über einen Relay-Server herzustellen. \nWenn Sie eine Relay-Verbindung beim ersten Versuch herstellen möchten, können Sie das Suffix \"/r\" an die ID anhängen oder die Option \"Immer über Relay-Server verbinden\" auf der Gegenstelle auswählen."),
|
||||
("Reconnect", "Erneut verbinden"),
|
||||
("Codec", "Codec"),
|
||||
("Resolution", "Auflösung"),
|
||||
("No transfers in progress", "Keine Übertragungen im Gange"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("setup_server_tip", "For faster connection, please set up your own server"),
|
||||
("Auto Login", "Auto Login (Only valid if you set \"Lock after session end\")"),
|
||||
("whitelist_tip", "Only whitelisted IP can access me"),
|
||||
("whitelist_sep", "Seperated by comma, semicolon, spaces or new line"),
|
||||
("whitelist_sep", "Separated by comma, semicolon, spaces or new line"),
|
||||
("Wrong credentials", "Wrong username or password"),
|
||||
("invalid_http", "must start with http:// or https://"),
|
||||
("install_daemon_tip", "For starting on boot, you need to install system service."),
|
||||
@@ -39,5 +39,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("verification_tip", "A new device has been detected, and a verification code has been sent to the registered email address, enter the verification code to continue logging in."),
|
||||
("software_render_tip", "If you have an Nvidia graphics card and the remote window closes immediately after connecting, installing the nouveau driver and choosing to use software rendering may help. A software restart is required."),
|
||||
("config_input", "In order to control remote desktop with keyboard, you need to grant RustDesk \"Input Monitoring\" permissions."),
|
||||
("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."),
|
||||
("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."),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Muta"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Aŭdia enigo"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Malkonekti"),
|
||||
("Tags", "Etikedi"),
|
||||
("Search ID", "Serĉi ID"),
|
||||
("Current Wayland display server is not supported", "La aktuala bilda servilo Wayland ne estas subtenita"),
|
||||
("whitelist_sep", "Vi povas uzi komon, punktokomon, spacon aŭ linsalton kiel apartigilo"),
|
||||
("Add ID", "Aldoni identigilo"),
|
||||
("Add Tag", "Aldoni etikedo"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", ""),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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!"),
|
||||
("Privacy Statement", ""),
|
||||
("Privacy Statement", "Declaración de privacidad"),
|
||||
("Mute", "Silenciar"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Entrada de audio"),
|
||||
("Enhancements", "Mejoras"),
|
||||
("Hardware Codec", "Códec de hardware"),
|
||||
@@ -51,13 +59,12 @@ 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"),
|
||||
("Too frequent", "Demasiado frecuente"),
|
||||
("Cancel", "Cancelar"),
|
||||
("Skip", "Saltar"),
|
||||
("Skip", "Omitir"),
|
||||
("Close", "Cerrar"),
|
||||
("Retry", "Reintentar"),
|
||||
("OK", ""),
|
||||
@@ -206,19 +213,18 @@ 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"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Verify", "Verificar"),
|
||||
("Remember me", "Recordarme"),
|
||||
("Trust this device", "Confiar en este dispositivo"),
|
||||
("Verification code", "Código de verificación"),
|
||||
("verification_tip", "Se ha detectado un nuevo dispositivo y se ha enviado un código de verificación a la dirección de correo registrada. Introduzca el código de verificación para continuar con el inicio de sesión."),
|
||||
("Logout", "Salir"),
|
||||
("Tags", "Tags"),
|
||||
("Search ID", "Buscar ID"),
|
||||
("Current Wayland display server is not supported", "El servidor de visualización actual de Wayland no es compatible"),
|
||||
("whitelist_sep", "Separados por coma, punto y coma, espacio o nueva línea"),
|
||||
("Add ID", "Agregar ID"),
|
||||
("Add Tag", "Agregar tag"),
|
||||
@@ -305,7 +311,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Language", "Idioma"),
|
||||
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
|
||||
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("android_open_battery_optimizations_tip", "Si deseas deshabilitar esta característica, por favor, ve a la página siguiente de ajustes, busca y entra en [Batería] y desmarca [Sin restricción]"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Conexión no disponible"),
|
||||
("Legacy mode", "Modo heredado"),
|
||||
("Map mode", "Modo mapa"),
|
||||
@@ -318,8 +326,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Restart Remote Device", "Reiniciar dispositivo"),
|
||||
("Are you sure you want to restart", "Esta Seguro que desea reiniciar?"),
|
||||
("Restarting Remote Device", "Reiniciando dispositivo remoto"),
|
||||
("remote_restarting_tip", "Dispositivo remoto reiniciando, favor de cerrar este mensaje y reconectarse con la contraseña permamente despues de un momento."),
|
||||
("Copied", ""),
|
||||
("remote_restarting_tip", "El dispositivo remoto se está reiniciando. Por favor cierre este mensaje y vuelva a conectarse con la contraseña peremanente en unos momentos."),
|
||||
("Copied", "Copiado"),
|
||||
("Exit Fullscreen", "Salir de pantalla completa"),
|
||||
("Fullscreen", "Pantalla completa"),
|
||||
("Mobile Actions", "Acciones móviles"),
|
||||
@@ -373,8 +381,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Deny LAN Discovery", "Denegar descubrimiento de LAN"),
|
||||
("Write a message", "Escribir un mensaje"),
|
||||
("Prompt", ""),
|
||||
("Please wait for confirmation of UAC...", ""),
|
||||
("elevated_foreground_window_tip", ""),
|
||||
("Please wait for confirmation of UAC...", "Por favor, espera confirmación de UAC"),
|
||||
("elevated_foreground_window_tip", "La ventana actual del escritorio remoto necesita privilegios elevados para funcionar, así que no puedes usar ratón y teclado temporalmente. Puedes solicitar al usuario remoto que minimize la ventana actual o hacer clic en el botón de elevación de la ventana de gestión de conexión. Para evitar este problema, se recomienda instalar el programa en el dispositivo remto."),
|
||||
("Disconnected", "Desconectado"),
|
||||
("Other", "Otro"),
|
||||
("Confirm before closing multiple tabs", "Confirmar antes de cerrar múltiples pestañas"),
|
||||
@@ -403,15 +411,53 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("hide_cm_tip", "Permitir ocultar solo si se aceptan sesiones a través de contraseña y usando contraseña permanente"),
|
||||
("wayland_experiment_tip", "El soporte para Wayland está en fase experimental, por favor, use X11 si necesita acceso desatendido."),
|
||||
("Right click to select tabs", "Clic derecho para seleccionar pestañas"),
|
||||
("Skipped", ""),
|
||||
("Skipped", "Omitido"),
|
||||
("Add to Address Book", "Añadir a la libreta de direcciones"),
|
||||
("Group", "Grupo"),
|
||||
("Search", "Búsqueda"),
|
||||
("Closed manually by the web console", "Cerrado manualmente por la consola web"),
|
||||
("Closed manually by web console", "Cerrado manualmente por la consola web"),
|
||||
("Local keyboard type", "Tipo de teclado local"),
|
||||
("Select local keyboard type", "Seleccionar tipo de teclado local"),
|
||||
("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\"."),
|
||||
].iter().cloned().collect();
|
||||
("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"),
|
||||
("Ask the remote user for authentication", "Pida autenticación al usuario remoto"),
|
||||
("Choose this if the remote account is administrator", "Elegir si la cuenta remota es de administrador"),
|
||||
("Transmit the username and password of administrator", "Transmitir usuario y contraseña del administrador"),
|
||||
("still_click_uac_tip", "Aún se necesita que el usuario remoto haga click en OK en la ventana UAC del RusDesk en ejecución."),
|
||||
("Request Elevation", "Solicitar Elevación"),
|
||||
("wait_accept_uac_tip", "Por favor espere a que el usuario remoto acepte el diálogo UAC."),
|
||||
("Elevate successfully", "Elevar con éxito"),
|
||||
("uppercase", "mayúsculas"),
|
||||
("lowercase", "minúsculas"),
|
||||
("digit", "dígito"),
|
||||
("special character", "carácter especial"),
|
||||
("length>=8", "longitud>=8"),
|
||||
("Weak", "Débil"),
|
||||
("Medium", "Media"),
|
||||
("Strong", "Fuerte"),
|
||||
("Switch Sides", "Intercambiar lados"),
|
||||
("Please confirm if you want to share your desktop?", "Por favor, confirma si quieres compartir tu escritorio"),
|
||||
("Display", "Pantalla"),
|
||||
("Default View Style", "Estilo de vista predeterminado"),
|
||||
("Default Scroll Style", "Estilo de desplazamiento predeterminado"),
|
||||
("Default Image Quality", "Calidad de imagen predeterminada"),
|
||||
("Default Codec", "Códec predeterminado"),
|
||||
("Bitrate", "Tasa de bits"),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", "Otras opciones predeterminadas"),
|
||||
("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", "Códec"),
|
||||
("Resolution", "Resolución"),
|
||||
("No transfers in progress", "No hay transferencias en curso"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
100
src/lang/fa.rs
100
src/lang/fa.rs
@@ -37,21 +37,28 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is empty", "کلیپبورد خالی است"),
|
||||
("Stop service", "توقف سرویس"),
|
||||
("Change ID", "تعویض شناسه"),
|
||||
("Your new ID", "جدید ID"),
|
||||
("length %min% to %max%", "%max% تا %min% طول از"),
|
||||
("starts with a letter", "با حرف شروع می شود"),
|
||||
("allowed characters", "کارکترهای مجاز"),
|
||||
("id_change_tip", "شناسه باید طبق این شرایط باشد : حروف کوچک و بزرگ انگلیسی و اعداد از 0 تا 9، _ و همچنین حرف اول آن فقط حروف بزرگ یا کوچک انگلیسی و طول آن بین 6 الی 16 کاراکتر باشد"),
|
||||
("Website", "وب سایت"),
|
||||
("About", "درباره"),
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Slogan_tip", "ساخته شده با قلب(عشق) در این دنیای پر هرج و مرج!"),
|
||||
("Privacy Statement", "بیانیه حریم خصوصی"),
|
||||
("Mute", "بستن صدا"),
|
||||
("Build Date", "تاریخ ساخت"),
|
||||
("Version", "نسخه"),
|
||||
("Home", "صفحه اصلی"),
|
||||
("Audio Input", "ورودی صدا"),
|
||||
("Enhancements", "بهبودها"),
|
||||
("Hardware Codec", "کدک سخت افزاری"),
|
||||
("Adaptive Bitrate", ""),
|
||||
("Adaptive Bitrate", "سازگار Bitrate"),
|
||||
("ID Server", "شناسه سرور"),
|
||||
("Relay Server", "Relay سرور"),
|
||||
("API Server", "API سرور"),
|
||||
("invalid_http", "شروع شود http:// یا https:// باید با"),
|
||||
("Invalid IP", "نامعتبر است IP آدرس"),
|
||||
("id_change_tip", "شناسه باید طبق این شرایط باشد : حروف کوچک و بزرگ انگلیسی و اعداد از 0 تا 9، _ و همچنین حرف اول آن فقط حروف بزرگ یا کوچک انگلیسی و طول آن بین 6 الی 16 کاراکتر باشد"),
|
||||
("Invalid format", "فرمت نادرست است"),
|
||||
("server_not_support", "هنوز توسط سرور مورد نظر پشتیبانی نمی شود"),
|
||||
("Not available", "در دسترسی نیست"),
|
||||
@@ -186,7 +193,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logging in...", "...در حال ورود"),
|
||||
("Enable RDP session sharing", "اشتراک گذاری جلسه RDP را فعال کنید"),
|
||||
("Auto Login", "ورود خودکار"),
|
||||
("Enable Direct IP Access", "دسترسی مستقیم IP را فعال کنید"),
|
||||
("Enable Direct IP Access", "را فعال کنید IP دسترسی مستقیم"),
|
||||
("Rename", "تغییر نام"),
|
||||
("Space", "فضا"),
|
||||
("Create Desktop Shortcut", "ساخت میانبر روی دسکتاپ"),
|
||||
@@ -206,19 +213,18 @@ 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", "ورود"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Verify", "تأیید کنید"),
|
||||
("Remember me", "مرا به یاد داشته باش"),
|
||||
("Trust this device", "به این دستگاه اعتماد کنید"),
|
||||
("Verification code", "کد تایید"),
|
||||
("verification_tip", "یک دستگاه جدید شناسایی شده است و یک کد تأیید به آدرس ایمیل ثبت شده ارسال شده است، برای ادامه ورود، کد تأیید را وارد کنید."),
|
||||
("Logout", "خروج"),
|
||||
("Tags", "برچسب ها"),
|
||||
("Search ID", "جستجوی شناسه"),
|
||||
("Current Wayland display server is not supported", "پشتیبانی نمی شود Wayland سرور نمایش فعلی"),
|
||||
("whitelist_sep", "با کاما، نقطه ویرگول، فاصله یا خط جدید از هم جدا می شوند"),
|
||||
("Add ID", "افزودن شناسه"),
|
||||
("Add Tag", "افزودن برچسب"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "را در پس زمینه نگه دارید RustDesk سرویس"),
|
||||
("Ignore Battery Optimizations", "بهینه سازی باتری نادیده گرفته شود"),
|
||||
("android_open_battery_optimizations_tip", "به صفحه تنظیمات بعدی بروید"),
|
||||
("Start on Boot", "در هنگام بوت شروع شود"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "سرویس اشتراکگذاری صفحه را در بوت راهاندازی کنید، به مجوزهای خاصی نیاز دارد"),
|
||||
("Connection not allowed", "اتصال مجاز نیست"),
|
||||
("Legacy mode", "legacy حالت"),
|
||||
("Map mode", "map حالت"),
|
||||
@@ -349,7 +357,7 @@ 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?", "همه دستگاه ها قطع شوند؟"),
|
||||
@@ -373,8 +381,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Deny LAN Discovery", "غیر فعالسازی جستجو در شبکه"),
|
||||
("Write a message", "یک پیام بنویسید"),
|
||||
("Prompt", ""),
|
||||
("Please wait for confirmation of UAC...", ""),
|
||||
("elevated_foreground_window_tip", ""),
|
||||
("Please wait for confirmation of UAC...", "باشید UAC لطفا منتظر تایید"),
|
||||
("elevated_foreground_window_tip", "پنجره فعلی دسکتاپ راه دور برای کار کردن به دسترسی بالاتری نیاز دارد، بنابراین نمیتواند به طور موقت از ماوس و صفحه کلید استفاده کند. می توانید از کاربر راه دور درخواست کنید که پنجره فعلی را به پایین منتقل کند یا روی دکمه ارتقاء دسترسی در پنجره مدیریت اتصال کلیک کنید. برای جلوگیری از این مشکل، توصیه می شود نرم افزار را روی دستگاه از راه دور نصب کنید."),
|
||||
("Disconnected", "قطع ارتباط"),
|
||||
("Other", "سایر"),
|
||||
("Confirm before closing multiple tabs", "تایید بستن دسته ای برگه ها"),
|
||||
@@ -383,13 +391,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Screen Share", "اشتراک گذاری صفحه"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "نیازمند اوبونتو نسخه 21.04 یا بالاتر است Wayland"),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "استفاده کنید و یا سیستم عامل خود را تغییر دهید X11 نیازمند نسخه بالاتری از توزیع لینوکس است. لطفا از دسکتاپ با سیستم"),
|
||||
("JumpLink", ""),
|
||||
("JumpLink", "چشم انداز"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "لطفاً صفحهای را برای اشتراکگذاری انتخاب کنید (در سمت همتا به همتا کار کنید)."),
|
||||
("Show RustDesk", "RustDesk نمایش"),
|
||||
("This PC", "This PC"),
|
||||
("or", "یا"),
|
||||
("Continue with", "ادامه با"),
|
||||
("Elevate", "افزایش سطح"),
|
||||
("Elevate", "ارتقاء"),
|
||||
("Zoom cursor", " بزرگنمایی نشانگر ماوس"),
|
||||
("Accept sessions via password", "قبول درخواست با رمز عبور"),
|
||||
("Accept sessions via click", "قبول درخواست با کلیک موس"),
|
||||
@@ -403,15 +411,53 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("hide_cm_tip", "فقط در صورت پذیرفتن جلسات از طریق رمز عبور و استفاده از رمز عبور دائمی، مخفی شدن مجاز است"),
|
||||
("wayland_experiment_tip", "پشتیبانی Wayland در مرحله آزمایشی است، لطفاً در صورت نیاز به دسترسی بدون مراقبت از X11 استفاده کنید."),
|
||||
("Right click to select tabs", "برای انتخاب تب ها راست کلیک کنید"),
|
||||
("Skipped", ""),
|
||||
("Skipped", "رد شد"),
|
||||
("Add to Address Book", "افزودن به دفترچه آدرس"),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("Group", "گروه"),
|
||||
("Search", "جستجو"),
|
||||
("Closed manually by web console", "به صورت دستی توسط کنسول وب بسته شد"),
|
||||
("Local keyboard type", "نوع صفحه کلید محلی"),
|
||||
("Select local keyboard type", "نوع صفحه کلید محلی را انتخاب کنید"),
|
||||
("software_render_tip", "اگر کارت گرافیک Nvidia دارید و پنجره راه دور بلافاصله پس از اتصال بسته می شود، درایور nouveau را نصب نمایید و انتخاب گزینه استفاده از رندر نرم افزار می تواند کمک کننده باشد. راه اندازی مجدد نرم افزار مورد نیاز است."),
|
||||
("Always use software rendering", "همیشه از رندر نرم افزاری استفاده کنید"),
|
||||
("config_input", "برای کنترل دسکتاپ از راه دور با صفحه کلید، باید مجوز RustDesk \"Input Monitoring\" را بدهید."),
|
||||
("config_microphone", "را بدهید. RustDesk \"Record Audio\" برای صحبت از راه دور، باید مجوز"),
|
||||
("request_elevation_tip", "همچنین می توانید در صورت وجود شخصی در سمت راه دور درخواست ارتقاء دسترسی دهید."),
|
||||
("Wait", "صبر کنید"),
|
||||
("Elevation Error", "خطای ارتقاء دسترسی"),
|
||||
("Ask the remote user for authentication", "درخواست احراز هویت از یک کاربر راه دور"),
|
||||
("Choose this if the remote account is administrator", "اگر حساب راه دور یک مدیر است، این را انتخاب کنید"),
|
||||
("Transmit the username and password of administrator", "نام کاربری و رمز عبور مدیر را منتقل کنید"),
|
||||
("still_click_uac_tip", "همچنان کاربر از راه دور نیاز دارد که روی OK در پنجره UAC اجرای RustDesk کلیک کند."),
|
||||
("Request Elevation", "درخواست ارتقاء دسترسی"),
|
||||
("wait_accept_uac_tip", "لطفاً منتظر بمانید تا کاربر راه دور درخواست پنجره UAC را بپذیرد."),
|
||||
("Elevate successfully", "ارتقاء دسترسی با موفقیت انجام شد"),
|
||||
("uppercase", "حروف بزرگ"),
|
||||
("lowercase", "حروف کوچک"),
|
||||
("digit", "عدد"),
|
||||
("special character", "کاراکتر خاص"),
|
||||
("length>=8", "حداقل طول 8 کاراکتر"),
|
||||
("Weak", "ضعیف"),
|
||||
("Medium", "متوسط"),
|
||||
("Strong", "قوی"),
|
||||
("Switch Sides", "طرفین را عوض کنید"),
|
||||
("Please confirm if you want to share your desktop?", "لطفاً تأیید کنید که آیا می خواهید دسکتاپ خود را به اشتراک بگذارید؟"),
|
||||
("Display", "نمایش دادن"),
|
||||
("Default View Style", "سبک نمایش پیش فرض"),
|
||||
("Default Scroll Style", "سبک پیش فرض اسکرول"),
|
||||
("Default Image Quality", "کیفیت تصویر پیش فرض"),
|
||||
("Default Codec", "کدک پیش فرض"),
|
||||
("Bitrate", "میزان بیت صفحه نمایش"),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "خودکار"),
|
||||
("Other Default Options", "سایر گزینه های پیش فرض"),
|
||||
("Voice call", "تماس صوتی"),
|
||||
("Text chat", "گفتگو متنی (چت متنی)"),
|
||||
("Stop voice call", "توقف تماس صوتی"),
|
||||
("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر میخواهید فوراً از سرور رله استفاده کنید، میتوانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"),
|
||||
("Reconnect", "اتصال مجدد"),
|
||||
("Codec", "کدک"),
|
||||
("Resolution", "وضوح"),
|
||||
("No transfers in progress", "هیچ انتقالی در حال انجام نیست"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
100
src/lang/fr.rs
100
src/lang/fr.rs
@@ -14,8 +14,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Service is not running", "Le service ne fonctionne pas"),
|
||||
("not_ready_status", "Pas prêt, veuillez vérifier la connexion réseau"),
|
||||
("Control Remote Desktop", "Contrôler le bureau à distance"),
|
||||
("Transfer File", "Transférer le fichier"),
|
||||
("Connect", "Connecter"),
|
||||
("Transfer File", "Transfert de fichiers"),
|
||||
("Connect", "Se connecter"),
|
||||
("Recent Sessions", "Sessions récentes"),
|
||||
("Address Book", "Carnet d'adresses"),
|
||||
("Confirmation", "Confirmation"),
|
||||
@@ -37,11 +37,19 @@ 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!"),
|
||||
("Privacy Statement", "Déclaration de confidentialité"),
|
||||
("Mute", "Muet"),
|
||||
("Build Date", "Date de compilation"),
|
||||
("Version", "Version"),
|
||||
("Home", "Accueil"),
|
||||
("Audio Input", "Entrée audio"),
|
||||
("Enhancements", "Améliorations"),
|
||||
("Hardware Codec", "Transcodage matériel"),
|
||||
@@ -51,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"),
|
||||
@@ -60,7 +67,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Skip", "Ignorer"),
|
||||
("Close", "Fermer"),
|
||||
("Retry", "Réessayer"),
|
||||
("OK", "Confirmer"),
|
||||
("OK", "Valider"),
|
||||
("Password Required", "Mot de passe requis"),
|
||||
("Please enter your password", "Veuillez saisir votre mot de passe"),
|
||||
("Remember password", "Mémoriser le mot de passe"),
|
||||
@@ -77,12 +84,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connected, waiting for image...", "Connecté, en attente de transmission d'image..."),
|
||||
("Name", "Nom"),
|
||||
("Type", "Type"),
|
||||
("Modified", "Modifié"),
|
||||
("Modified", "Modifié le"),
|
||||
("Size", "Taille"),
|
||||
("Show Hidden Files", "Afficher les fichiers cachés"),
|
||||
("Receive", "Accepter"),
|
||||
("Receive", "Recevoir"),
|
||||
("Send", "Envoyer"),
|
||||
("Refresh File", "Actualiser le fichier"),
|
||||
("Refresh File", "Rafraîchir le contenu"),
|
||||
("Local", "Local"),
|
||||
("Remote", "Distant"),
|
||||
("Remote Computer", "Ordinateur distant"),
|
||||
@@ -90,7 +97,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Confirm Delete", "Confirmer la suppression"),
|
||||
("Delete", "Supprimer"),
|
||||
("Properties", "Propriétés"),
|
||||
("Multi Select", "Choix multiple"),
|
||||
("Multi Select", "Sélection multiple"),
|
||||
("Select All", "Tout sélectionner"),
|
||||
("Unselect All", "Tout déselectionner"),
|
||||
("Empty Directory", "Répertoire vide"),
|
||||
@@ -123,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show quality monitor", "Afficher le moniteur de qualité"),
|
||||
("Disable clipboard", "Désactiver le presse-papier"),
|
||||
("Lock after session end", "Verrouiller l'ordinateur distant après la déconnexion"),
|
||||
("Insert", "Insérer"),
|
||||
("Insert", "Envoyer"),
|
||||
("Insert Lock", "Verrouiller l'ordinateur distant"),
|
||||
("Refresh", "Rafraîchir l'écran"),
|
||||
("ID does not exist", "L'ID n'existe pas"),
|
||||
@@ -206,19 +213,18 @@ 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", "Seul l'IP dans la liste blanche peut accéder à mon appareil"),
|
||||
("whitelist_tip", "Seule une IP de la liste blanche peut accéder à mon appareil"),
|
||||
("Login", "Connexion"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Verify", "Vérifier"),
|
||||
("Remember me", "Se souvenir de moi"),
|
||||
("Trust this device", "Faire confiance à cet appareil"),
|
||||
("Verification code", "Code de vérification"),
|
||||
("verification_tip", "Un nouvel appareil a été détecté et un code de vérification a été envoyé à l'adresse e-mail enregistrée, entrez le code de vérification pour continuer la connexion."),
|
||||
("Logout", "Déconnexion"),
|
||||
("Tags", "Étiqueter"),
|
||||
("Search ID", "Rechercher un ID"),
|
||||
("Current Wayland display server is not supported", "Le serveur d'affichage Wayland n'est pas pris en charge"),
|
||||
("whitelist_sep", "Vous pouvez utiliser une virgule, un point-virgule, un espace ou une nouvelle ligne comme séparateur"),
|
||||
("Add ID", "Ajouter un ID"),
|
||||
("Add Tag", "Ajouter une balise"),
|
||||
@@ -269,7 +275,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Chat", "Discuter"),
|
||||
("Total", "Total"),
|
||||
("items", "éléments"),
|
||||
("Selected", "Choisi"),
|
||||
("Selected", "Sélectionné"),
|
||||
("Screen Capture", "Capture d'écran"),
|
||||
("Input Control", "Contrôle de saisie"),
|
||||
("Audio Capture", "Capture audio"),
|
||||
@@ -289,7 +295,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Overwrite", "Écraser"),
|
||||
("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"),
|
||||
("Quit", "Quitter"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/fr/manual/mac/#enable-permissions"),
|
||||
("Help", "Aider"),
|
||||
("Failed", "échouer"),
|
||||
("Succeeded", "Succès"),
|
||||
@@ -303,9 +309,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "en mode privé"),
|
||||
("Out privacy mode", "hors mode de confidentialité"),
|
||||
("Language", "Langue"),
|
||||
("Keep RustDesk background service", "Gardez le service Rustdesk service arrière plan"),
|
||||
("Keep RustDesk background service", "Gardez le service RustDesk en arrière plan"),
|
||||
("Ignore Battery Optimizations", "Ignorer les optimisations batterie"),
|
||||
("android_open_battery_optimizations_tip", "Conseil android d'optimisation de batterie"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Connexion non autorisée"),
|
||||
("Legacy mode", "Mode hérité"),
|
||||
("Map mode", ""),
|
||||
@@ -356,14 +364,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clear", "Effacer"),
|
||||
("Audio Input Device", "Périphérique source audio"),
|
||||
("Deny remote access", "Interdir l'accès distant"),
|
||||
("Use IP Whitelisting", "Utiliser liste blanche d'IP"),
|
||||
("Use IP Whitelisting", "Utiliser une liste blanche d'IP"),
|
||||
("Network", "Réseau"),
|
||||
("Enable RDP", "Activer RDP"),
|
||||
("Pin menubar", "Épingler la barre de menus"),
|
||||
("Unpin menubar", "Détacher la barre de menu"),
|
||||
("Recording", "Enregistrement"),
|
||||
("Directory", "Répertoire"),
|
||||
("Automatically record incoming sessions", "Enregistrement automatique des session entrantes"),
|
||||
("Automatically record incoming sessions", "Enregistrement automatique des sessions entrantes"),
|
||||
("Change", "Modifier"),
|
||||
("Start session recording", "Commencer l'enregistrement"),
|
||||
("Stop session recording", "Stopper l'enregistrement"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", "Ajouter au carnet d'adresses"),
|
||||
("Group", "Groupe"),
|
||||
("Search", "Rechercher"),
|
||||
("Closed manually by the web console", "Fermé manuellement par la console Web"),
|
||||
("Closed manually by web console", "Fermé manuellement par la console Web"),
|
||||
("Local keyboard type", "Disposition du clavier local"),
|
||||
("Select local keyboard type", "Selectionner la disposition du clavier local"),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("software_render_tip", "Si vous avez une carte graphique NVIDIA et que la fenêtre distante se ferme immédiatement après la connexion, l'installation du pilote Nouveau et le choix d'utiliser le rendu du logiciel peuvent aider. Un redémarrage du logiciel est requis."),
|
||||
("Always use software rendering", "Utiliser toujours le rendu logiciel"),
|
||||
("config_input", "Afin de contrôler le bureau à distance avec le clavier, vous devez accorder à RustDesk l'autorisation \"Surveillance de l’entrée\"."),
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", "Vous pouvez également demander une augmentation des privilèges s'il y a quelqu'un du côté distant."),
|
||||
("Wait", "En cours"),
|
||||
("Elevation Error", "Erreur d'augmentation des privilèges"),
|
||||
("Ask the remote user for authentication", "Demander à l'utilisateur distant de s'authentifier"),
|
||||
("Choose this if the remote account is administrator", "Choisissez ceci si le compte distant est le compte d'administrateur"),
|
||||
("Transmit the username and password of administrator", "Transmettre le nom d'utilisateur et le mot de passe de l'administrateur"),
|
||||
("still_click_uac_tip", "Nécessite toujours que l'utilisateur distant confirme par la fenêtre UAC de RustDesk en cours d'éxécution."),
|
||||
("Request Elevation", "Demande d'augmentation des privilèges"),
|
||||
("wait_accept_uac_tip", "Veuillez attendre que l'utilisateur distant accepte la boîte de dialogue UAC."),
|
||||
("Elevate successfully", "Augmentation des privilèges avec succès"),
|
||||
("uppercase", "majuscule"),
|
||||
("lowercase", "minuscule"),
|
||||
("digit", "chiffre"),
|
||||
("special character", "caractère spécial"),
|
||||
("length>=8", "longueur>=8"),
|
||||
("Weak", "Faible"),
|
||||
("Medium", "Moyen"),
|
||||
("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 ?"),
|
||||
("Display", "Affichage"),
|
||||
("Default View Style", "Style de vue par défaut"),
|
||||
("Default Scroll Style", "Style de défilement par défaut"),
|
||||
("Default Image Quality", "Qualité d'image par défaut"),
|
||||
("Default Codec", "Codec par défaut"),
|
||||
("Bitrate", "Débit"),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "Auto"),
|
||||
("Other Default Options", "Autres options par défaut"),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Σίγαση"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Είσοδος ήχου"),
|
||||
("Enhancements", "Βελτιώσεις"),
|
||||
("Hardware Codec", "Κωδικοποιητής υλικού"),
|
||||
@@ -51,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", "Μη διαθέσιμο"),
|
||||
@@ -206,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", "Σύνδεση"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Αποσύνδεση"),
|
||||
("Tags", "Ετικέτες"),
|
||||
("Search ID", "Αναζήτηση ID"),
|
||||
("Current Wayland display server is not supported", "Ο τρέχων διακομιστής εμφάνισης Wayland δεν υποστηρίζεται"),
|
||||
("whitelist_sep", "Διαχωρίζονται με κόμμα, ερωτηματικό, διάστημα ή νέα γραμμή"),
|
||||
("Add ID", "Προσθήκη αναγνωριστικού ID"),
|
||||
("Add Tag", "Προσθήκη ετικέτας"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"),
|
||||
("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"),
|
||||
("android_open_battery_optimizations_tip", "Θέλετε να ανοίξετε τις ρυθμίσεις βελτιστοποίησης μπαταρίας;"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Η σύνδεση απορρίφθηκε"),
|
||||
("Legacy mode", "Λειτουργία συμβατότητας"),
|
||||
("Map mode", "Map mode"),
|
||||
@@ -403,15 +411,53 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("hide_cm_tip", "Να επιτρέπεται η απόκρυψη, μόνο εάν αποδέχεστε συνδέσεις μέσω κωδικού πρόσβασης και χρησιμοποιείτε μόνιμο κωδικό πρόσβασης"),
|
||||
("wayland_experiment_tip", "Η υποστήριξη Wayland βρίσκεται σε πειραματικό στάδιο, χρησιμοποιήστε το X11 εάν χρειάζεστε πρόσβαση χωρίς επίβλεψη."),
|
||||
("Right click to select tabs", "Κάντε δεξί κλικ για να επιλέξετε καρτέλες"),
|
||||
("Skipped", ""),
|
||||
("Skipped", "Παράλειψη"),
|
||||
("Add to Address Book", "Προσθήκη στο Βιβλίο Διευθύνσεων"),
|
||||
("Group", "Ομάδα"),
|
||||
("Search", "Αναζήτηση"),
|
||||
("Closed manually by the web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("Closed manually by web console", "Κλειστό χειροκίνητα από την κονσόλα web"),
|
||||
("Local keyboard type", "Τύπος τοπικού πληκτρολογίου"),
|
||||
("Select local keyboard type", "Επιλογή τύπου τοπικού πληκτρολογίου"),
|
||||
("software_render_tip", "Εάν έχετε κάρτα γραφικών Nvidia και το παράθυρο σύνδεσης κλείνει αμέσως μετά τη σύνδεση, η εγκατάσταση του προγράμματος οδήγησης nouveau και η επιλογή χρήσης της επιτάχυνσης γραφικών μέσω λογισμικού μπορεί να βοηθήσει. Απαιτείται επανεκκίνηση."),
|
||||
("Always use software rendering", "Επιτάχυνση γραφικών μέσω λογισμικού"),
|
||||
("config_input", "Για να ελέγξετε την απομακρυσμένη επιφάνεια εργασίας με πληκτρολόγιο, πρέπει να εκχωρήσετε δικαιώματα στο RustDesk"),
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", "αίτημα ανύψωσης δικαιωμάτων χρήστη"),
|
||||
("Wait", "Περιμένετε"),
|
||||
("Elevation Error", "Σφάλμα ανύψωσης δικαιωμάτων χρήστη"),
|
||||
("Ask the remote user for authentication", "Ζητήστε από τον απομακρυσμένο χρήστη έλεγχο ταυτότητας"),
|
||||
("Choose this if the remote account is administrator", "Επιλέξτε αυτό εάν ο απομακρυσμένος λογαριασμός είναι διαχειριστής"),
|
||||
("Transmit the username and password of administrator", "Μεταβίβαση του ονόματος χρήστη και του κωδικού πρόσβασης του διαχειριστή"),
|
||||
("still_click_uac_tip", "Εξακολουθεί να απαιτεί από τον απομακρυσμένο χρήστη να κάνει κλικ στο OK στο παράθυρο UAC όπου εκτελείται το RustDesk."),
|
||||
("Request Elevation", "Αίτημα ανύψωσης δικαιωμάτων χρήστη"),
|
||||
("wait_accept_uac_tip", "Περιμένετε να αποδεχτεί ο απομακρυσμένος χρήστης το παράθυρο διαλόγου UAC."),
|
||||
("Elevate successfully", "Επιτυχής ανύψωση δικαιωμάτων χρήστη"),
|
||||
("uppercase", "κεφαλαία γράμματα"),
|
||||
("lowercase", "πεζά γράμματα"),
|
||||
("digit", "Αριθμός"),
|
||||
("special character", "ειδικός χαρακτήρας"),
|
||||
("length>=8", "μήκος>=8"),
|
||||
("Weak", "Αδύναμο"),
|
||||
("Medium", "Μέτριο"),
|
||||
("Strong", "Δυνατό"),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Némítás"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Hangátvitel"),
|
||||
("Enhancements", "Fejlesztések"),
|
||||
("Hardware Codec", "Hardware kodek"),
|
||||
@@ -51,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ő"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Kilépés"),
|
||||
("Tags", "Tagok"),
|
||||
("Search ID", "Azonosító keresése..."),
|
||||
("Current Wayland display server is not supported", "A Wayland display szerver nem támogatott"),
|
||||
("whitelist_sep", "A címeket veszővel, pontosvesszővel, szóközzel, vagy új sorral válassza el"),
|
||||
("Add ID", "Azonosító hozzáadása"),
|
||||
("Add Tag", "Címke hozzáadása"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "RustDesk futtatása a háttérben"),
|
||||
("Ignore Battery Optimizations", "Akkumulátorkímélő figyelmen kívűl hagyása"),
|
||||
("android_open_battery_optimizations_tip", "Ha le szeretné tiltani ezt a funkciót, lépjen a RustDesk alkalmazás beállítási oldalára, keresse meg az [Akkumulátorkímélő] lehetőséget és válassza a nincs korlátozás lehetőséget."),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "A csatlakozás nem engedélyezett"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", "Pernyataan Privasi"),
|
||||
("Mute", "Bisukan"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Masukkan Audio"),
|
||||
("Enhancements", "Peningkatan"),
|
||||
("Hardware Codec", "Codec Perangkat Keras"),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Keluar"),
|
||||
("Tags", "Tag"),
|
||||
("Search ID", "Cari ID"),
|
||||
("Current Wayland display server is not supported", "Server tampilan Wayland saat ini tidak didukung"),
|
||||
("whitelist_sep", "Dipisahkan dengan koma, titik koma, spasi, atau baris baru"),
|
||||
("Add ID", "Tambah ID"),
|
||||
("Add Tag", "Tambah Tag"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
|
||||
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Koneksi tidak dijinkan"),
|
||||
("Legacy mode", "Mode lama"),
|
||||
("Map mode", "Mode peta"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", "Pencarian"),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
102
src/lang/it.rs
102
src/lang/it.rs
@@ -13,7 +13,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Service is running", "Il servizio è in esecuzione"),
|
||||
("Service is not running", "Il servizio non è in esecuzione"),
|
||||
("not_ready_status", "Non pronto. Verifica la tua connessione"),
|
||||
("Control Remote Desktop", "Controlla una scrivania remota"),
|
||||
("Control Remote Desktop", "Controlla un desktop remoto"),
|
||||
("Transfer File", "Trasferisci file"),
|
||||
("Connect", "Connetti"),
|
||||
("Recent Sessions", "Sessioni recenti"),
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Slogan_tip", "Fatta con il cuore in questo mondo caotico!"),
|
||||
("Privacy Statement", "Informativa sulla privacy"),
|
||||
("Mute", "Silenzia"),
|
||||
("Build Date", "Data della build"),
|
||||
("Version", "Versione"),
|
||||
("Home", "Home"),
|
||||
("Audio Input", "Input audio"),
|
||||
("Enhancements", "Miglioramenti"),
|
||||
("Hardware Codec", "Codifica Hardware"),
|
||||
@@ -51,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"),
|
||||
@@ -185,7 +192,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enter your password", "Inserisci la tua password"),
|
||||
("Logging in...", "Autenticazione..."),
|
||||
("Enable RDP session sharing", "Abilita la condivisione della sessione RDP"),
|
||||
("Auto Login", "Login automatico"),
|
||||
("Auto Login", "Accesso automatico"),
|
||||
("Enable Direct IP Access", "Abilita l'accesso diretto tramite IP"),
|
||||
("Rename", "Rinomina"),
|
||||
("Space", "Spazio"),
|
||||
@@ -195,37 +202,36 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please enter the folder name", "Inserisci il nome della cartella"),
|
||||
("Fix it", "Risolvi"),
|
||||
("Warning", "Avviso"),
|
||||
("Login screen using Wayland is not supported", "La schermata di login non è supportata utilizzando Wayland"),
|
||||
("Login screen using Wayland is not supported", "La schermata di accesso non è supportata utilizzando Wayland"),
|
||||
("Reboot required", "Riavvio necessario"),
|
||||
("Unsupported display server ", "Display server non supportato"),
|
||||
("x11 expected", "x11 necessario"),
|
||||
("Port", "Porta"),
|
||||
("Settings", "Impostazioni"),
|
||||
("Username", " Nome utente"),
|
||||
("Invalid port", "Porta non valida"),
|
||||
("Invalid port", "Numero di porta non valido"),
|
||||
("Closed manually by the peer", "Chiuso manualmente dal peer"),
|
||||
("Enable remote configuration modification", "Abilita la modifica remota della configurazione"),
|
||||
("Run without install", "Avvia senza installare"),
|
||||
("Always connected via relay", "Connesso sempre tramite relay"),
|
||||
("Always connect via relay", "Connetti sempre tramite relay"),
|
||||
("Run without install", "Esegui senza installare"),
|
||||
("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"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Verify", "Verifica"),
|
||||
("Remember me", "Ricordami"),
|
||||
("Trust this device", "Registra questo dispositivo come attendibile"),
|
||||
("Verification code", "Codice di verifica"),
|
||||
("verification_tip", "È stato rilevato un nuovo dispositivo e un codice di verifica è stato inviato all'indirizzo e-mail registrato; inserire il codice di verifica per continuare l'accesso."),
|
||||
("Logout", "Esci"),
|
||||
("Tags", "Tag"),
|
||||
("Search ID", "Cerca ID"),
|
||||
("Current Wayland display server is not supported", "Questo display server Wayland non è supportato"),
|
||||
("whitelist_sep", "Separati da virgola, punto e virgola, spazio o a capo"),
|
||||
("Add ID", "Aggiungi ID"),
|
||||
("Add Tag", "Aggiungi tag"),
|
||||
("Unselect all tags", "Deseleziona tutti i tag"),
|
||||
("Network error", "Errore di rete"),
|
||||
("Username missed", "Nome utente dimenticato"),
|
||||
("Password missed", "Password dimenticata"),
|
||||
("Username missed", "Nome utente mancante"),
|
||||
("Password missed", "Password mancante"),
|
||||
("Wrong credentials", "Credenziali errate"),
|
||||
("Edit Tag", "Modifica tag"),
|
||||
("Unremember Password", "Dimentica password"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Mantieni il servizio di RustDesk in background"),
|
||||
("Ignore Battery Optimizations", "Ignora le ottimizzazioni della batteria"),
|
||||
("android_open_battery_optimizations_tip", "Se si desidera disabilitare questa funzione, andare nelle impostazioni dell'applicazione RustDesk, aprire la sezione [Batteria] e deselezionare [Senza restrizioni]."),
|
||||
("Start on Boot", "Avvia all'accensione"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "L'avvio del servizio di condivisione dello schermo all'accensione, richiede autorizzazioni speciali"),
|
||||
("Connection not allowed", "Connessione non consentita"),
|
||||
("Legacy mode", "Modalità legacy"),
|
||||
("Map mode", "Modalità mappa"),
|
||||
@@ -332,9 +340,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show Menubar", "Mostra la barra dei menu"),
|
||||
("Hide Menubar", "nascondi la barra dei menu"),
|
||||
("Direct Connection", "Connessione diretta"),
|
||||
("Relay Connection", "Collegamento a relè"),
|
||||
("Relay Connection", "Connessione relay"),
|
||||
("Secure Connection", "Connessione sicura"),
|
||||
("Insecure Connection", "Connessione insicura"),
|
||||
("Insecure Connection", "Connessione non sicura"),
|
||||
("Scale original", "Scala originale"),
|
||||
("Scale adaptive", "Scala adattiva"),
|
||||
("General", "Generale"),
|
||||
@@ -348,9 +356,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Unlock Security Settings", "Sblocca impostazioni di sicurezza"),
|
||||
("Enable Audio", "Abilita audio"),
|
||||
("Unlock Network Settings", "Sblocca impostazioni di rete"),
|
||||
("Server", ""),
|
||||
("Server", "Server"),
|
||||
("Direct IP Access", "Accesso IP diretto"),
|
||||
("Proxy", ""),
|
||||
("Proxy", "Proxy"),
|
||||
("Apply", "Applica"),
|
||||
("Disconnect all devices?", "Disconnettere tutti i dispositivi?"),
|
||||
("Clear", "Ripulisci"),
|
||||
@@ -372,7 +380,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable LAN Discovery", "Abilita il rilevamento della LAN"),
|
||||
("Deny LAN Discovery", "Nega il rilevamento della LAN"),
|
||||
("Write a message", "Scrivi un messaggio"),
|
||||
("Prompt", ""),
|
||||
("Prompt", "Richiede"),
|
||||
("Please wait for confirmation of UAC...", "Attendi la conferma dell'UAC..."),
|
||||
("elevated_foreground_window_tip", "La finestra corrente del desktop remoto richiede privilegi più elevati per funzionare, quindi non è in grado di utilizzare temporaneamente il mouse e la tastiera. È possibile chiedere all'utente remoto di ridurre a icona la finestra corrente o di fare clic sul pulsante di elevazione nella finestra di gestione della connessione. Per evitare questo problema, si consiglia di installare il software sul dispositivo remoto."),
|
||||
("Disconnected", "Disconnesso"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", "Aggiungi alla rubrica"),
|
||||
("Group", "Gruppo"),
|
||||
("Search", "Cerca"),
|
||||
("Closed manually by the web console", "Chiudi manualmente dalla console Web"),
|
||||
("Closed manually by web console", "Chiudi manualmente dalla console Web"),
|
||||
("Local keyboard type", "Tipo di tastiera locale"),
|
||||
("Select local keyboard type", "Seleziona il tipo di tastiera locale"),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("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", "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"),
|
||||
("Ask the remote user for authentication", "Chiedere l'autenticazione all'utente remoto"),
|
||||
("Choose this if the remote account is administrator", "Scegliere questa opzione se l'account remoto è amministratore"),
|
||||
("Transmit the username and password of administrator", "Trasmettere il nome utente e la password dell'amministratore"),
|
||||
("still_click_uac_tip", "Richiede ancora che l'utente remoto faccia clic su OK nella finestra UAC dell'esecuzione di RustDesk."),
|
||||
("Request Elevation", "Richiedi elevazione dei diritti"),
|
||||
("wait_accept_uac_tip", "Attendere che l'utente remoto accetti la finestra di dialogo UAC."),
|
||||
("Elevate successfully", "Elevazione dei diritti effettuata con successo"),
|
||||
("uppercase", "Maiuscola"),
|
||||
("lowercase", "Minuscola"),
|
||||
("digit", "Numero"),
|
||||
("special character", "Carattere speciale"),
|
||||
("length>=8", "Lunghezza >= 8"),
|
||||
("Weak", "Debole"),
|
||||
("Medium", "Media"),
|
||||
("Strong", "Forte"),
|
||||
("Switch Sides", "Cambia lato"),
|
||||
("Please confirm if you want to share your desktop?", "Vuoi condividere il tuo desktop?"),
|
||||
("Display", "Visualizzazione"),
|
||||
("Default View Style", "Stile Visualizzazione Predefinito"),
|
||||
("Default Scroll Style", "Stile Scorrimento Predefinito"),
|
||||
("Default Image Quality", "Qualità Immagine Predefinita"),
|
||||
("Default Codec", "Codec Predefinito"),
|
||||
("Bitrate", "Bitrate"),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "Auto"),
|
||||
("Other Default Options", "Altre Opzioni Predefinite"),
|
||||
("Voice call", "Chiamata vocale"),
|
||||
("Text chat", "Chat testuale"),
|
||||
("Stop voice call", "Interrompi la chiamata vocale"),
|
||||
("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", "Codec"),
|
||||
("Resolution", "Risoluzione"),
|
||||
("No transfers in progress", "Nessun trasferimento in corso"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "ミュート"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "音声入力デバイス"),
|
||||
("Enhancements", "追加機能"),
|
||||
("Hardware Codec", "ハードウェア コーデック"),
|
||||
@@ -51,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", "利用不可"),
|
||||
@@ -206,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", "ログイン"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "ログアウト"),
|
||||
("Tags", "タグ"),
|
||||
("Search ID", "IDを検索"),
|
||||
("Current Wayland display server is not supported", "現在のWaylandディスプレイサーバーはサポートされていません"),
|
||||
("whitelist_sep", "カンマやセミコロン、空白、改行で区切ってください"),
|
||||
("Add ID", "IDを追加"),
|
||||
("Add Tag", "タグを追加"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"),
|
||||
("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"),
|
||||
("android_open_battery_optimizations_tip", "この機能を使わない場合は、次のRestDeskアプリ設定ページから「バッテリー」に進み、「制限なし」の選択を外してください"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "接続が許可されていません"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "음소거"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "오디오 입력"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", "하드웨어 코덱"),
|
||||
@@ -51,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", "불가능"),
|
||||
@@ -206,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", "로그인"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "로그아웃"),
|
||||
("Tags", "태그"),
|
||||
("Search ID", "ID 검색"),
|
||||
("Current Wayland display server is not supported", "현재 Wayland 디스플레이 서버가 지원되지 않습니다"),
|
||||
("whitelist_sep", "다음 글자로 구분합니다. ',(콤마) ;(세미콜론) 띄어쓰기 혹은 줄바꿈'"),
|
||||
("Add ID", "ID 추가"),
|
||||
("Add Tag", "태그 추가"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "RustDesk 백그라운드 서비스로 유지하기"),
|
||||
("Ignore Battery Optimizations", "배터리 최적화 무시하기"),
|
||||
("android_open_battery_optimizations_tip", "해당 기능을 비활성화하려면 RustDesk 응용 프로그램 설정 페이지로 이동하여 [배터리]에서 [제한 없음] 선택을 해제하십시오."),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "연결이 허용되지 않음"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Дыбыссыздандыру"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Аудио Еңгізу"),
|
||||
("Enhancements", "Жақсартулар"),
|
||||
("Hardware Codec", "Hardware Codec"),
|
||||
@@ -51,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", "Қолжетімсіз"),
|
||||
@@ -206,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", "Кіру"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Шығу"),
|
||||
("Tags", "Тақтар"),
|
||||
("Search ID", "ID Іздеу"),
|
||||
("Current Wayland display server is not supported", "Ағымдағы Wayland дисплей серберіне қолдау көрсетілмейді"),
|
||||
("whitelist_sep", "Үтір, нүктелі үтір, бос орын және жаңа жолал арқылы бөлінеді"),
|
||||
("Add ID", "ID Қосу"),
|
||||
("Add Tag", "Тақ Қосу"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"),
|
||||
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
|
||||
("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Қосылу рұқсат етілмеген"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
463
src/lang/nl.rs
Normal file
463
src/lang/nl.rs
Normal file
@@ -0,0 +1,463 @@
|
||||
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"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("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", "Standaard 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", "Indien een directe verbinding niet mogelijk is, kunt u proberen verbinding te maken via een Relay Server. \nAls u bij de eerste poging een relaisverbinding tot stand wilt brengen, kunt u het achtervoegsel \"/r\" toevoegen aan het ID of de optie \"Altijd verbinden via relaisserver\" selecteren op de externe terminal."),
|
||||
("Reconnect", "Herverbinden"),
|
||||
("Codec", "Codec"),
|
||||
("Resolution", "Resolutie"),
|
||||
("No transfers in progress", "Geen overdrachten in uitvoering"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
151
src/lang/pl.rs
151
src/lang/pl.rs
@@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Status"),
|
||||
("Your Desktop", "Twój pulpit"),
|
||||
("desk_tip", "W celu zestawienia połączenia z tym urządzeniem należy poniższego ID i hasła."),
|
||||
("desk_tip", "W celu połączenia się z tym urządzeniem należy użyć poniższego ID i hasła"),
|
||||
("Password", "Hasło"),
|
||||
("Ready", "Gotowe"),
|
||||
("Established", "Nawiązano"),
|
||||
@@ -37,11 +37,19 @@ 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"),
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", ""),
|
||||
("About", "O aplikacji"),
|
||||
("Slogan_tip", "Tworzone z miłością w tym pełnym chaosu świecie!"),
|
||||
("Privacy Statement", "Oświadczenie o ochronie prywatności"),
|
||||
("Mute", "Wycisz"),
|
||||
("Build Date", "Zbudowano"),
|
||||
("Version", "Wersja"),
|
||||
("Home", "Pulpit"),
|
||||
("Audio Input", "Wejście audio"),
|
||||
("Enhancements", "Ulepszenia"),
|
||||
("Hardware Codec", "Kodek sprzętowy"),
|
||||
@@ -51,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"),
|
||||
@@ -96,7 +103,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Empty Directory", "Pusty katalog"),
|
||||
("Not an empty directory", "Katalog nie jest pusty"),
|
||||
("Are you sure you want to delete this file?", "Czy na pewno chcesz usunąć ten plik?"),
|
||||
("Are you sure you want to delete this empty directory?", "Czy na pewno chcesz usunać ten pusty katalog?"),
|
||||
("Are you sure you want to delete this empty directory?", "Czy na pewno chcesz usunąć ten pusty katalog?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Czy na pewno chcesz usunąć pliki z tego katalogu?"),
|
||||
("Do this for all conflicts", "wykonaj dla wszystkich konfliktów"),
|
||||
("This is irreversible!", "To jest nieodwracalne!"),
|
||||
@@ -118,7 +125,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Good image quality", "Dobra jakość obrazu"),
|
||||
("Balanced", "Zrównoważony"),
|
||||
("Optimize reaction time", "Zoptymalizuj czas reakcji"),
|
||||
("Custom", "Własne"),
|
||||
("Custom", "Niestandardowe"),
|
||||
("Show remote cursor", "Pokazuj zdalny kursor"),
|
||||
("Show quality monitor", "Parametry połączenia"),
|
||||
("Disable clipboard", "Wyłącz schowek"),
|
||||
@@ -138,10 +145,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to make direct connection to remote desktop", "Nie udało się nawiązać bezpośredniego połączenia z pulpitem zdalnym"),
|
||||
("Set Password", "Ustaw hasło"),
|
||||
("OS Password", "Hasło systemu operacyjnego"),
|
||||
("install_tip", "RustDesk może nie działać poprawnie na maszynie zdalnej z przyczyn związanych z UAC. W celu uniknięcią problemów z UAC, kliknij poniższy przycisk by zainstalować RustDesk w swoim systemie."),
|
||||
("install_tip", "RustDesk może nie działać poprawnie na maszynie zdalnej z przyczyn związanych z UAC. W celu uniknięcia problemów z UAC, kliknij poniższy przycisk by zainstalować RustDesk w swoim systemie."),
|
||||
("Click to upgrade", "Zaktualizuj"),
|
||||
("Click to download", "Pobierz"),
|
||||
("Click to update", "Uaktualinij"),
|
||||
("Click to update", "Uaktualnij"),
|
||||
("Configure", "Konfiguruj"),
|
||||
("config_acc", "Konfiguracja konta"),
|
||||
("config_screen", "Konfiguracja ekranu"),
|
||||
@@ -206,19 +213,18 @@ 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", "Zezwlaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
|
||||
("whitelist_tip", "Zezwalaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
|
||||
("Login", "Zaloguj"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Verify", "Zweryfikuj"),
|
||||
("Remember me", "Zapamiętaj mnie"),
|
||||
("Trust this device", "Dodaj to urządzenie do zaufanych"),
|
||||
("Verification code", "Kod weryfikacyjny"),
|
||||
("verification_tip", "Nastąpiło logowanie z nowego urządzenia, kod weryfikacyjny został wysłany na podany adres email, wprowadź kod by kontynuować proces logowania"),
|
||||
("Logout", "Wyloguj"),
|
||||
("Tags", "Tagi"),
|
||||
("Search ID", "Szukaj ID"),
|
||||
("Current Wayland display server is not supported", "Obecny serwer wyświetlania Wayland nie jest obsługiwany"),
|
||||
("whitelist_sep", "Oddzielone przecinkiem, średnikiem, spacją lub w nowej linii"),
|
||||
("Add ID", "Dodaj ID"),
|
||||
("Add Tag", "Dodaj Tag"),
|
||||
@@ -232,7 +238,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Favorites", "Ulubione"),
|
||||
("Add to Favorites", "Dodaj do ulubionych"),
|
||||
("Remove from Favorites", "Usuń z ulubionych"),
|
||||
("Empty", "Pusty"),
|
||||
("Empty", "Pusto"),
|
||||
("Invalid folder name", "Nieprawidłowa nazwa folderu"),
|
||||
("Socks5 Proxy", "Socks5 Proxy"),
|
||||
("Hostname", "Nazwa hosta"),
|
||||
@@ -268,8 +274,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("OPEN", "Otwórz"),
|
||||
("Chat", "Czat"),
|
||||
("Total", "Łącznie"),
|
||||
("items", "elementy"),
|
||||
("Selected", "Zaznaczone"),
|
||||
("items", "elementów"),
|
||||
("Selected", "Zaznaczonych"),
|
||||
("Screen Capture", "Przechwytywanie ekranu"),
|
||||
("Input Control", "Kontrola wejścia"),
|
||||
("Audio Capture", "Przechwytywanie dźwięku"),
|
||||
@@ -278,13 +284,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you accept?", "Akceptujesz?"),
|
||||
("Open System Setting", "Otwórz ustawienia systemowe"),
|
||||
("How to get Android input permission?", "Jak uzyskać uprawnienia do wprowadzania danych w systemie Android?"),
|
||||
("android_input_permission_tip1", "android_input_permission_tip1"),
|
||||
("android_input_permission_tip2", "android_input_permission_tip2"),
|
||||
("android_new_connection_tip", "android_new_connection_tip"),
|
||||
("android_service_will_start_tip", "android_service_will_start_tip"),
|
||||
("android_stop_service_tip", "android_stop_service_tip"),
|
||||
("android_version_audio_tip", "android_version_audio_tip"),
|
||||
("android_start_service_tip", "android_start_service_tip"),
|
||||
("android_input_permission_tip1", "Aby można było sterować Twoim urządzeniem za pomocą myszy lub dotyku, musisz zezwolić RustDesk na korzystanie z usługi \"Ułatwienia dostępu\"."),
|
||||
("android_input_permission_tip2", "Przejdź do następnej strony ustawień systemowych, znajdź i wejdź w [Zainstalowane usługi], włącz usługę [RustDesk Input]."),
|
||||
("android_new_connection_tip", "Otrzymano nowe żądanie zdalnego dostępu, które chce przejąć kontrolę nad Twoim urządzeniem."),
|
||||
("android_service_will_start_tip", "Włączenie opcji „Przechwytywanie ekranu” spowoduje automatyczne uruchomienie usługi, umożliwiając innym urządzeniom żądanie połączenia z Twoim urządzeniem."),
|
||||
("android_stop_service_tip", "Zamknięcie usługi spowoduje automatyczne zamknięcie wszystkich nawiązanych połączeń."),
|
||||
("android_version_audio_tip", "Bieżąca wersja systemu Android nie obsługuje przechwytywania dźwięku, zaktualizuj system do wersji Android 10 lub nowszej."),
|
||||
("android_start_service_tip", "Kliknij [Uruchom usługę] lub Otwórz [Przechwytywanie ekranu], aby uruchomić usługę udostępniania ekranu."),
|
||||
("Account", "Konto"),
|
||||
("Overwrite", "Nadpisz"),
|
||||
("This file exists, skip or overwrite this file?", "Ten plik istnieje, pominąć czy nadpisać ten plik?"),
|
||||
@@ -305,7 +311,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Language", "Język"),
|
||||
("Keep RustDesk background service", "Zachowaj usługę RustDesk w tle"),
|
||||
("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"),
|
||||
("android_open_battery_optimizations_tip", "android_open_battery_optimizations_tip"),
|
||||
("android_open_battery_optimizations_tip", "Jeśli chcesz wyłączyć tę funkcję, przejdź do następnej strony ustawień aplikacji RustDesk, znajdź i wprowadź [Bateria], odznacz [Bez ograniczeń]"),
|
||||
("Start on Boot", "Autostart"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Uruchom usługę udostępniania ekranu podczas startu, wymaga specjalnych uprawnień"),
|
||||
("Connection not allowed", "Połączenie niedozwolone"),
|
||||
("Legacy mode", "Tryb kompatybilności wstecznej (legacy)"),
|
||||
("Map mode", "Tryb mapowania"),
|
||||
@@ -331,7 +339,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scroll Style", "Styl przewijania"),
|
||||
("Show Menubar", "Pokaż pasek menu"),
|
||||
("Hide Menubar", "Ukryj pasek menu"),
|
||||
("Direct Connection", "Połącznie bezpośrednie"),
|
||||
("Direct Connection", "Połączenie bezpośrednie"),
|
||||
("Relay Connection", "Połączenie przez bramkę"),
|
||||
("Secure Connection", "Połączenie szyfrowane"),
|
||||
("Insecure Connection", "Połączenie nieszyfrowane"),
|
||||
@@ -343,13 +351,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Dark Theme", "Ciemny motyw"),
|
||||
("Dark", "Ciemny"),
|
||||
("Light", "Jasny"),
|
||||
("Follow System", "Zgodne z systemem"),
|
||||
("Enable hardware codec", "Włącz wsparcie sprzętowe dla kodeków"),
|
||||
("Unlock Security Settings", "Odblokuj Ustawienia Zabezpieczeń"),
|
||||
("Enable Audio", "Włącz Dźwięk"),
|
||||
("Follow System", "Zgodny z systemem"),
|
||||
("Enable hardware codec", "Włącz akcelerację sprzętową kodeków"),
|
||||
("Unlock Security Settings", "Odblokuj ustawienia zabezpieczeń"),
|
||||
("Enable Audio", "Włącz dźwięk"),
|
||||
("Unlock Network Settings", "Odblokuj ustawienia Sieciowe"),
|
||||
("Server", "Serwer"),
|
||||
("Direct IP Access", "Bezpośredni Adres IP"),
|
||||
("Direct IP Access", "Bezpośredni adres IP"),
|
||||
("Proxy", "Proxy"),
|
||||
("Apply", "Zastosuj"),
|
||||
("Disconnect all devices?", "Czy rozłączyć wszystkie urządzenia?"),
|
||||
@@ -361,20 +369,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable RDP", "Włącz RDP"),
|
||||
("Pin menubar", "Przypnij pasek menu"),
|
||||
("Unpin menubar", "Odepnij pasek menu"),
|
||||
("Recording", "Trwa nagrywanie"),
|
||||
("Recording", "Nagrywanie"),
|
||||
("Directory", "Katalog"),
|
||||
("Automatically record incoming sessions", "Automatycznie nagrywaj sesje przychodzące"),
|
||||
("Change", "Zmień"),
|
||||
("Start session recording", "Zacznij nagrywać sesję"),
|
||||
("Stop session recording", "Zatrzymaj nagrywanie sesji"),
|
||||
("Enable Recording Session", "Włącz Nagrywanie Sesji"),
|
||||
("Enable Recording Session", "Włącz nagrywanie Sesji"),
|
||||
("Allow recording session", "Zezwól na nagrywanie sesji"),
|
||||
("Enable LAN Discovery", "Włącz wykrywanie urządzenia w sieci LAN"),
|
||||
("Deny LAN Discovery", "Zablokuj wykrywanie urządzenia w sieci LAN"),
|
||||
("Write a message", "Napisz wiadomość"),
|
||||
("Prompt", "Monit"),
|
||||
("Please wait for confirmation of UAC...", "Oczekuje potwierdzenia ustawień UAC"),
|
||||
("elevated_foreground_window_tip", ""),
|
||||
("Please wait for confirmation of UAC...", "Poczekaj na potwierdzenie uprawnień UAC"),
|
||||
("elevated_foreground_window_tip", "Aktualne okno zdalnego urządzenia wymaga wyższych uprawnień by poprawnie działać, chwilowo niemożliwym jest korzystanie z myszy i klawiatury. Możesz poprosić zdalnego użytkownika o minimalizację okna, lub nacisnąć przycisk podniesienia uprawnień w oknie zarządzania połączeniami. By uniknąć tego problemu, rekomendujemy instalację oprogramowania na urządzeniu zdalnym."),
|
||||
("Disconnected", "Rozłączone"),
|
||||
("Other", "Inne"),
|
||||
("Confirm before closing multiple tabs", "Potwierdź przed zamknięciem wielu kart"),
|
||||
@@ -382,7 +390,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Full Access", "Pełny dostęp"),
|
||||
("Screen Share", "Udostępnianie ekranu"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland wymaga Ubuntu 21.04 lub nowszego."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland wymaga wyższej wersji dystrybucji Linuksa. Wypróbuj pulpit X11 lub zmień system operacyjny."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland wymaga nowszej dystrybucji Linuksa. Wypróbuj pulpit X11 lub zmień system operacyjny."),
|
||||
("JumpLink", "View"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Wybierz ekran do udostępnienia (działaj po stronie równorzędnej)."),
|
||||
("Show RustDesk", "Pokaż RustDesk"),
|
||||
@@ -390,7 +398,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("or", "albo"),
|
||||
("Continue with", "Kontynuuj z"),
|
||||
("Elevate", "Uzyskaj uprawnienia"),
|
||||
("Zoom cursor", "Zoom kursora"),
|
||||
("Zoom cursor", "Powiększenie kursora"),
|
||||
("Accept sessions via password", "Uwierzytelnij sesję używając hasła"),
|
||||
("Accept sessions via click", "Uwierzytelnij sesję poprzez kliknięcie"),
|
||||
("Accept sessions via both", "Uwierzytelnij sesję za pomocą obu sposobów"),
|
||||
@@ -400,18 +408,57 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("One-time password length", "Długość hasła jednorazowego"),
|
||||
("Request access to your device", "Żądanie dostępu do Twojego urządzenia"),
|
||||
("Hide connection management window", "Ukryj okno zarządzania połączeniem"),
|
||||
("hide_cm_tip", ""),
|
||||
("wayland_experiment_tip", ""),
|
||||
("Right click to select tabs", ""),
|
||||
("Skipped", ""),
|
||||
("hide_cm_tip", "Pozwalaj na ukrycie tylko gdy akceptujesz sesje za pośrednictwem hasła i używasz hasła permanentnego"),
|
||||
("wayland_experiment_tip", "Wsparcie dla Wayland jest niekompletne, użyj X11 jeżeli chcesz korzystać z dostępu nienadzorowanego"),
|
||||
("Right click to select tabs", "Kliknij prawym przyciskiem myszy by wybrać zakładkę"),
|
||||
("Skipped", "Pominięte"),
|
||||
("Add to Address Book", "Dodaj do Książki Adresowej"),
|
||||
("Group", "Grypy"),
|
||||
("Group", "Grupy"),
|
||||
("Search", "Szukaj"),
|
||||
("Closed manually by the web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("Closed manually by web console", "Zakończone manualnie z konsoli Web"),
|
||||
("Local keyboard type", "Lokalny typ klawiatury"),
|
||||
("Select local keyboard type", "Wybierz lokalny typ klawiatury"),
|
||||
("software_render_tip", "Jeżeli posiadasz kartę graficzną Nvidia i okno zamyka się natychmiast po nawiązaniu połączenia, instalacja sterownika nouveau i wybór renderowania programowego mogą pomóc. Restart aplikacji jest wymagany."),
|
||||
("Always use software rendering", "Zawsze używaj renderowania programowego"),
|
||||
("config_input", "By kontrolować zdalne urządzenie przy pomocy klawiatury, musisz udzielić aplikacji RustDesk uprawnień do \"Urządzeń Wejściowych\"."),
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", "Możesz poprosić o podniesienie uprawnień jeżeli ktoś posiada dostęp do zdalnego urządzenia."),
|
||||
("Wait", "Czekaj"),
|
||||
("Elevation Error", "Błąd przy podnoszeniu uprawnień"),
|
||||
("Ask the remote user for authentication", "Poproś użytkownika zdalnego o uwierzytelnienie"),
|
||||
("Choose this if the remote account is administrator", "Wybierz to jeżeli zdalne konto jest administratorem"),
|
||||
("Transmit the username and password of administrator", "Prześlij nazwę użytkownika i hasło administratora"),
|
||||
("still_click_uac_tip", "Nadal wymaga od zdalnego użytkownika potwierdzenia uprawnień UAC."),
|
||||
("Request Elevation", "Poproś o podniesienie uprawnień"),
|
||||
("wait_accept_uac_tip", "Prosimy czekać aż zdalny użytkownik potwierdzi uprawnienia UAC."),
|
||||
("Elevate successfully", "Pomyślnie podniesiono uprawnienia"),
|
||||
("uppercase", "wielkie litery"),
|
||||
("lowercase", "małe litery"),
|
||||
("digit", "cyfra"),
|
||||
("special character", "znak specjalny"),
|
||||
("length>=8", "długość>=8"),
|
||||
("Weak", "Słabe"),
|
||||
("Medium", "Średnie"),
|
||||
("Strong", "Mocne"),
|
||||
("Switch Sides", "Zamień Strony"),
|
||||
("Please confirm if you want to share your desktop?", "Czy na pewno chcesz udostępnić swój ekran?"),
|
||||
("Display", "Wyświetlanie"),
|
||||
("Default View Style", "Domyślny styl wyświetlania"),
|
||||
("Default Scroll Style", "Domyślny styl przewijania"),
|
||||
("Default Image Quality", "Domyślna jakość obrazu"),
|
||||
("Default Codec", "Dokyślny kodek"),
|
||||
("Bitrate", "Bitrate"),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "Auto"),
|
||||
("Other Default Options", "Inne opcje domyślne"),
|
||||
("Voice call", "Rozmowa głosowa"),
|
||||
("Text chat", "Chat tekstowy"),
|
||||
("Stop voice call", "Rozłącz"),
|
||||
("relay_hint_tip", "Bezpośrednie połączenie może nie być możliwe, możesz spróbować połączyć się przez serwer przekazujący. \nDodatkowo, jeśli chcesz użyć serwera przekazującego przy pierwszej próbie, możesz dodać sufiks \"/r\" do identyfikatora lub wybrać opcję \"Zawsze łącz przez serwer przekazujący\" na karcie peer-ów."),
|
||||
("Reconnect", "Połącz ponownie"),
|
||||
("Codec", "Kodek"),
|
||||
("Resolution", "Rozdzielczość"),
|
||||
("Key", "Klucz"),
|
||||
("No transfers in progress", "Brak transferów w toku"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Silenciar"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Entrada de Áudio"),
|
||||
("Enhancements", "Melhorias"),
|
||||
("Hardware Codec", ""),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Sair"),
|
||||
("Tags", "Tags"),
|
||||
("Search ID", "Procurar ID"),
|
||||
("Current Wayland display server is not supported", "Servidor de display Wayland atual não é suportado"),
|
||||
("whitelist_sep", "Separado por vírcula, ponto-e-vírgula, espaços ou nova linha"),
|
||||
("Add ID", "Adicionar ID"),
|
||||
("Add Tag", "Adicionar Tag"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Manter o serviço RustDesk em funcionamento"),
|
||||
("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Ligação não autorizada"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Desativar som"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Entrada de Áudio"),
|
||||
("Enhancements", "Melhorias"),
|
||||
("Hardware Codec", "Codec de hardware"),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Sair"),
|
||||
("Tags", "Tags"),
|
||||
("Search ID", "Pesquisar ID"),
|
||||
("Current Wayland display server is not supported", "Servidor de display Wayland atual não é suportado"),
|
||||
("whitelist_sep", "Separado por vírcula, ponto-e-vírgula, espaços ou nova linha"),
|
||||
("Add ID", "Adicionar ID"),
|
||||
("Add Tag", "Adicionar Tag"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Manter o serviço do RustDesk executando em segundo plano"),
|
||||
("Ignore Battery Optimizations", "Ignorar otimizações de bateria"),
|
||||
("android_open_battery_optimizations_tip", "Abrir otimizações de bateria"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Conexão não permitida"),
|
||||
("Legacy mode", "Modo legado"),
|
||||
("Map mode", "Modo mapa"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
463
src/lang/ro.rs
Normal file
463
src/lang/ro.rs
Normal file
@@ -0,0 +1,463 @@
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Stare"),
|
||||
("Your Desktop", "Desktopul tău"),
|
||||
("desk_tip", "Desktopul tău poate fi accesat folosind ID-ul și parola de mai jos."),
|
||||
("Password", "Parola"),
|
||||
("Ready", "Pregătit"),
|
||||
("Established", "Stabilit"),
|
||||
("connecting_status", "În curs de conectare la rețeaua RustDesk..."),
|
||||
("Enable Service", "Activează serviciu"),
|
||||
("Start Service", "Pornește serviciu"),
|
||||
("Service is running", "Serviciul este în curs de executare..."),
|
||||
("Service is not running", "Serviciul nu funcționează"),
|
||||
("not_ready_status", "Nepregătit. Verifică conexiunea la rețea."),
|
||||
("Control Remote Desktop", "Controlează desktop-ul la distanță"),
|
||||
("Transfer File", "Transferă fișier"),
|
||||
("Connect", "Conectează-te"),
|
||||
("Recent Sessions", "Sesiuni recente"),
|
||||
("Address Book", "Agendă"),
|
||||
("Confirmation", "Confirmare"),
|
||||
("TCP Tunneling", "Tunel TCP"),
|
||||
("Remove", "Elimină"),
|
||||
("Refresh random password", "Actualizează parolă aleatorie"),
|
||||
("Set your own password", "Setează propria parolă"),
|
||||
("Enable Keyboard/Mouse", "Activează control tastatură/mouse"),
|
||||
("Enable Clipboard", "Activează clipboard"),
|
||||
("Enable File Transfer", "Activează transfer fișiere"),
|
||||
("Enable TCP Tunneling", "Activează tunel TCP"),
|
||||
("IP Whitelisting", "Listă de IP-uri autorizate"),
|
||||
("ID/Relay Server", "Server de ID/retransmisie"),
|
||||
("Import Server Config", "Importă configurație server"),
|
||||
("Export Server Config", "Exportă configurație server"),
|
||||
("Import server configuration successfully", "Configurație server importată cu succes"),
|
||||
("Export server configuration successfully", "Configurație server exportată cu succes"),
|
||||
("Invalid server configuration", "Configurație server nevalidă"),
|
||||
("Clipboard is empty", "Clipboard gol"),
|
||||
("Stop service", "Oprește serviciu"),
|
||||
("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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Fără sunet"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Intrare audio"),
|
||||
("Enhancements", "Îmbunătățiri"),
|
||||
("Hardware Codec", "Codec hardware"),
|
||||
("Adaptive Bitrate", "Rată de biți adaptabilă"),
|
||||
("ID Server", "Server de ID"),
|
||||
("Relay Server", "Server de retransmisie"),
|
||||
("API Server", "Server API"),
|
||||
("invalid_http", "Trebuie să înceapă cu http:// sau https://"),
|
||||
("Invalid IP", "IP nevalid"),
|
||||
("Invalid format", "Format nevalid"),
|
||||
("server_not_support", "Încă nu este compatibil cu serverul"),
|
||||
("Not available", "Indisponibil"),
|
||||
("Too frequent", "Modificat prea frecvent"),
|
||||
("Cancel", "Anulează"),
|
||||
("Skip", "Omite"),
|
||||
("Close", "Închide"),
|
||||
("Retry", "Reîncearcă"),
|
||||
("OK", "OK"),
|
||||
("Password Required", "Parolă necesară"),
|
||||
("Please enter your password", "Introdu parola"),
|
||||
("Remember password", "Memorează parola"),
|
||||
("Wrong Password", "Parolă incorectă"),
|
||||
("Do you want to enter again?", "Vrei să intri din nou?"),
|
||||
("Connection Error", "Eroare de conexiune"),
|
||||
("Error", "Eroare"),
|
||||
("Reset by the peer", "Conexiunea a fost închisă de dispozitivul pereche"),
|
||||
("Connecting...", "Conectare..."),
|
||||
("Connection in progress. Please wait.", "Conectare în curs. Te rugăm așteaptă."),
|
||||
("Please try 1 minute later", "Reîncearcă într-un minut"),
|
||||
("Login Error", "Eroare de autentificare"),
|
||||
("Successful", "Succes"),
|
||||
("Connected, waiting for image...", "Conectat, se așteaptă transmiterea imaginii..."),
|
||||
("Name", "Denumire"),
|
||||
("Type", "Tip"),
|
||||
("Modified", "Modificat"),
|
||||
("Size", "Dimensiune"),
|
||||
("Show Hidden Files", "Afișează fișiere ascunse"),
|
||||
("Receive", "Acceptă"),
|
||||
("Send", "Trimite"),
|
||||
("Refresh File", "Actualizează fișier"),
|
||||
("Local", "Local"),
|
||||
("Remote", "La distanță"),
|
||||
("Remote Computer", "Computer la distanță"),
|
||||
("Local Computer", "Computer local"),
|
||||
("Confirm Delete", "Confirmă ștergerea"),
|
||||
("Delete", "Șterge"),
|
||||
("Properties", "Caracteristici"),
|
||||
("Multi Select", "Alegere multiplă"),
|
||||
("Select All", "Selectează tot"),
|
||||
("Unselect All", "Deselectează tot"),
|
||||
("Empty Directory", "Director gol"),
|
||||
("Not an empty directory", "Directorul nu este gol"),
|
||||
("Are you sure you want to delete this file?", "Sigur vrei să ștergi acest fișier?"),
|
||||
("Are you sure you want to delete this empty directory?", "Sigur vrei să ștergi acest director gol?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Sigur vrei să ștergi fișierul din acest director?"),
|
||||
("Do this for all conflicts", "Aplică pentru toate conflictele"),
|
||||
("This is irreversible!", "Această acțiune este ireversibilă!"),
|
||||
("Deleting", "În curs de ștergere..."),
|
||||
("files", "fișier"),
|
||||
("Waiting", "În așteptare..."),
|
||||
("Finished", "Finalizat"),
|
||||
("Speed", "Viteză"),
|
||||
("Custom Image Quality", "Setează calitatea imaginii"),
|
||||
("Privacy mode", "Mod privat"),
|
||||
("Block user input", "Blochează utilizator"),
|
||||
("Unblock user input", "Deblochează utilizator"),
|
||||
("Adjust Window", "Ajustează fereastra"),
|
||||
("Original", "Dimensiune originală"),
|
||||
("Shrink", "Micșorează"),
|
||||
("Stretch", "Extinde"),
|
||||
("Scrollbar", "Bară de derulare"),
|
||||
("ScrollAuto", "Derulare automată"),
|
||||
("Good image quality", "Calitate bună a imaginii"),
|
||||
("Balanced", "Calitate normală a imaginii"),
|
||||
("Optimize reaction time", "Optimizează timpul de reacție"),
|
||||
("Custom", "Personalizat"),
|
||||
("Show remote cursor", "Afișează cursor la distanță"),
|
||||
("Show quality monitor", "Afișează indicator de calitate"),
|
||||
("Disable clipboard", "Dezactivează clipboard"),
|
||||
("Lock after session end", "Blochează după deconectare"),
|
||||
("Insert", "Introdu"),
|
||||
("Insert Lock", "Blochează computer"),
|
||||
("Refresh", "Reîmprospătează"),
|
||||
("ID does not exist", "ID neexistent"),
|
||||
("Failed to connect to rendezvous server", "Conectare la server rendezvous eșuată"),
|
||||
("Please try later", "Încearcă mai târziu"),
|
||||
("Remote desktop is offline", "Desktopul la distanță este offline"),
|
||||
("Key mismatch", "Nepotrivire chei"),
|
||||
("Timeout", "Conexiune expirată"),
|
||||
("Failed to connect to relay server", "Conectare la server de retransmisie eșuată"),
|
||||
("Failed to connect via rendezvous server", "Conectare prin intermediul serverului rendezvous eșuată"),
|
||||
("Failed to connect via relay server", "Conectare prin intermediul serverului de retransmisie eșuată"),
|
||||
("Failed to make direct connection to remote desktop", "Imposibil de stabilit o conexiune directă cu desktopul la distanță"),
|
||||
("Set Password", "Setează parola"),
|
||||
("OS Password", "Parolă OS"),
|
||||
("install_tip", "Din cauza restricțiilor UAC, e posibil ca RustDesk să nu funcționeze corespunzător. Pentru a evita acest lucru, fă clic pe butonul de mai jos pentru a instala RustDesk."),
|
||||
("Click to upgrade", "Fă clic pentru a face upgrade"),
|
||||
("Click to download", "Fă clic pentru a descărca"),
|
||||
("Click to update", "Fă clic pentru a actualiza"),
|
||||
("Configure", "Configurează"),
|
||||
("config_acc", "Pentru a controla desktopul la distanță, trebuie să permiți RustDesk acces la setările de Accesibilitate."),
|
||||
("config_screen", "Pentru a controla desktopul la distanță, trebuie să permiți RustDesk acces la setările de Înregistrare ecran."),
|
||||
("Installing ...", "Instalare în curs..."),
|
||||
("Install", "Instalează"),
|
||||
("Installation", "Instalare"),
|
||||
("Installation Path", "Cale de instalare"),
|
||||
("Create start menu shortcuts", "Creează comenzi rapide în meniul Start"),
|
||||
("Create desktop icon", "Creează pictogramă pe desktop"),
|
||||
("agreement_tip", "Începerea procesului de instalare înseamnă acceptarea acordului de licență."),
|
||||
("Accept and Install", "Acceptă și instalează"),
|
||||
("End-user license agreement", "Acord de licență pentru utilizatorul final"),
|
||||
("Generating ...", "Se generează..."),
|
||||
("Your installation is lower version.", "Versiunea instalată este una inferioară."),
|
||||
("not_close_tcp_tip", "Nu închide această fereastră în timp ce folosești tunelul"),
|
||||
("Listening ...", "În așteptarea conexiunii tunel..."),
|
||||
("Remote Host", "Gazdă la distanță"),
|
||||
("Remote Port", "Port la distanță"),
|
||||
("Action", "Acțiune"),
|
||||
("Add", "Adaugă"),
|
||||
("Local Port", "Port local"),
|
||||
("Local Address", "Adresă locală"),
|
||||
("Change Local Port", "Schimbă port local"),
|
||||
("setup_server_tip", "Pentru o conexiune mai rapidă, îți poți configura propriul server."),
|
||||
("Too short, at least 6 characters.", "Prea scurt; trebuie cel puțin 6 caractere."),
|
||||
("The confirmation is not identical.", "Cele două intrări nu corespund."),
|
||||
("Permissions", "Permisiuni"),
|
||||
("Accept", "Acceptă"),
|
||||
("Dismiss", "Respinge"),
|
||||
("Disconnect", "Deconectează-te"),
|
||||
("Allow using keyboard and mouse", "Permite utilizarea tastaturii și mouse-ului"),
|
||||
("Allow using clipboard", "Permite utilizarea clipboardului"),
|
||||
("Allow hearing sound", "Permite auzirea sunetului"),
|
||||
("Allow file copy and paste", "Permite copierea/lipirea fișierelor"),
|
||||
("Connected", "Conectat"),
|
||||
("Direct and encrypted connection", "Conexiune directă criptată"),
|
||||
("Relayed and encrypted connection", "Conexiune retransmisă criptată"),
|
||||
("Direct and unencrypted connection", "Conexiune directă necriptată"),
|
||||
("Relayed and unencrypted connection", "Conexiune retransmisă necriptată"),
|
||||
("Enter Remote ID", "Introdu ID-ul dispozitivului la distanță"),
|
||||
("Enter your password", "Introdu parola"),
|
||||
("Logging in...", "Se conectează..."),
|
||||
("Enable RDP session sharing", "Activează partajarea sesiunii RDP"),
|
||||
("Auto Login", "Conectare automată (valid doar dacă funcția de Blocare după deconectare este activă)"),
|
||||
("Enable Direct IP Access", "Activează accesul direct cu IP"),
|
||||
("Rename", "Redenumește"),
|
||||
("Space", "Spațiu"),
|
||||
("Create Desktop Shortcut", "Creează comandă rapidă de desktop"),
|
||||
("Change Path", "Schimbă calea"),
|
||||
("Create Folder", "Creează folder"),
|
||||
("Please enter the folder name", "Introdu numele folderului"),
|
||||
("Fix it", "Repară"),
|
||||
("Warning", "Avertisment"),
|
||||
("Login screen using Wayland is not supported", "Ecranele de conectare care folosesc Wayland nu sunt acceptate"),
|
||||
("Reboot required", "Repornire necesară"),
|
||||
("Unsupported display server ", "Tipul de server de afișaj nu este acceptat"),
|
||||
("x11 expected", "E necesar X11"),
|
||||
("Port", "Port"),
|
||||
("Settings", "Setări"),
|
||||
("Username", " Nume de utilizator"),
|
||||
("Invalid port", "Port nevalid"),
|
||||
("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"),
|
||||
("Connect via relay", ""),
|
||||
("Always connect via relay", "Se conectează mereu prin retransmisie"),
|
||||
("whitelist_tip", "Doar adresele IP autorizate pot accesa acest dispozitiv"),
|
||||
("Login", "Conectare"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Logout", "Deconectare"),
|
||||
("Tags", "Etichetare"),
|
||||
("Search ID", "Caută după ID"),
|
||||
("whitelist_sep", "Poți folosi ca separator virgula, punctul și virgula, spațiul sau linia nouă"),
|
||||
("Add ID", "Adaugă ID"),
|
||||
("Add Tag", "Adaugă etichetă"),
|
||||
("Unselect all tags", "Deselectează toate etichetele"),
|
||||
("Network error", "Eroare de rețea"),
|
||||
("Username missed", "Lipsește numele de utilizator"),
|
||||
("Password missed", "Lipsește parola"),
|
||||
("Wrong credentials", "Nume sau parolă greșită"),
|
||||
("Edit Tag", "Modifică etichetă"),
|
||||
("Unremember Password", "Uită parola"),
|
||||
("Favorites", "Favorite"),
|
||||
("Add to Favorites", "Adaugă la Favorite"),
|
||||
("Remove from Favorites", "Șterge din Favorite"),
|
||||
("Empty", "Gol"),
|
||||
("Invalid folder name", "Denumire folder nevalidă"),
|
||||
("Socks5 Proxy", "Proxy Socks5"),
|
||||
("Hostname", "Nume gazdă"),
|
||||
("Discovered", "Descoperite"),
|
||||
("install_daemon_tip", "Pentru executare la pornirea sistemului, instalează serviciul de sistem."),
|
||||
("Remote ID", "ID dispozitiv la distanță"),
|
||||
("Paste", "Lipește"),
|
||||
("Paste here?", "Lipește aici?"),
|
||||
("Are you sure to close the connection?", "Sigur vrei să închizi conexiunea?"),
|
||||
("Download new version", "Descarcă noua versiune"),
|
||||
("Touch mode", "Mod tactil"),
|
||||
("Mouse mode", "Mod mouse"),
|
||||
("One-Finger Tap", "Apasă cu un deget"),
|
||||
("Left Mouse", "Clic stânga"),
|
||||
("One-Long Tap", "Apasă lung"),
|
||||
("Two-Finger Tap", "Apasă cu două degete"),
|
||||
("Right Mouse", "Clic dreapta"),
|
||||
("One-Finger Move", "Mișcă cu un deget"),
|
||||
("Double Tap & Move", "Apasă dublu și mișcă"),
|
||||
("Mouse Drag", "Tragere mouse"),
|
||||
("Three-Finger vertically", "Trei degete vertical"),
|
||||
("Mouse Wheel", "Rotiță mouse"),
|
||||
("Two-Finger Move", "Mișcă cu două degete"),
|
||||
("Canvas Move", "Mută ecran"),
|
||||
("Pinch to Zoom", "Apropie degetele pentru zoom"),
|
||||
("Canvas Zoom", "Zoom ecran"),
|
||||
("Reset canvas", "Reinițializează ecranul"),
|
||||
("No permission of file transfer", "Nicio permisiune pentru transferul de fișiere"),
|
||||
("Note", "Reține"),
|
||||
("Connection", "Conexiune"),
|
||||
("Share Screen", "Partajează ecran"),
|
||||
("CLOSE", "ÎNCHIDE"),
|
||||
("OPEN", "DESCHIDE"),
|
||||
("Chat", "Discută"),
|
||||
("Total", "Total"),
|
||||
("items", "elemente"),
|
||||
("Selected", "Selectat"),
|
||||
("Screen Capture", "Captură ecran"),
|
||||
("Input Control", "Control intrări"),
|
||||
("Audio Capture", "Captură audio"),
|
||||
("File Connection", "Conexiune fișier"),
|
||||
("Screen Connection", "Conexiune ecran"),
|
||||
("Do you accept?", "Accepți?"),
|
||||
("Open System Setting", "Deschide setări sistem"),
|
||||
("How to get Android input permission?", "Cum autorizez dispozitive de intrare pe Android?"),
|
||||
("android_input_permission_tip1", "Pentru ca un dispozitiv la distanță să poată controla un dispozitiv Android folosind mouse-ul sau suportul tactil, trebuie să permiți RustDesk să utilize serviciul Accesibilitate."),
|
||||
("android_input_permission_tip2", "Accesează următoarea pagină din Setări, caută și deschide [Aplicații instalate] și activează serviciul [RustDesk Input]."),
|
||||
("android_new_connection_tip", "Ai primit o nouă solicitare de control pentru dispozitivul actual."),
|
||||
("android_service_will_start_tip", "Activarea setării Captură ecran va porni automat serviciul, permițând altor dispozitive să solicite conectarea la dispozitivul tău."),
|
||||
("android_stop_service_tip", "Închiderea serviciului va închide automat toate conexiunile stabilite."),
|
||||
("android_version_audio_tip", "Versiunea actuală de Android nu suportă captura audio. Fă upgrade la Android 10 sau la o versiune superioară."),
|
||||
("android_start_service_tip", "Apasă [Pornește serviciu] sau DESCHIDE [Captură ecran] pentru a porni serviciul de partajare a ecranului."),
|
||||
("Account", "Cont"),
|
||||
("Overwrite", "Suprascrie"),
|
||||
("This file exists, skip or overwrite this file?", "Fișier deja existent. Omite sau suprascrie?"),
|
||||
("Quit", "Ieși"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Ajutor"),
|
||||
("Failed", "Nereușit"),
|
||||
("Succeeded", "Reușit"),
|
||||
("Someone turns on privacy mode, exit", "Cineva activează modul privat, ieși din"),
|
||||
("Unsupported", "Neacceptat"),
|
||||
("Peer denied", "Dispozitiv pereche refuzat"),
|
||||
("Please install plugins", "Instalează pluginuri"),
|
||||
("Peer exit", "Ieșire dispozitiv pereche"),
|
||||
("Failed to turn off", "Dezactivare nereușită"),
|
||||
("Turned off", "Închis"),
|
||||
("In privacy mode", "În modul privat"),
|
||||
("Out privacy mode", "Ieșit din modul privat"),
|
||||
("Language", "Limbă"),
|
||||
("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"),
|
||||
("Ignore Battery Optimizations", "Ignoră optimizările de baterie"),
|
||||
("android_open_battery_optimizations_tip", "Pentru dezactivarea acestei funcții, accesează setările aplicației RustDesk, deschide secțiunea [Baterie] și deselectează [Fără restricții]."),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Conexiune neautoriztă"),
|
||||
("Legacy mode", "Mod legacy"),
|
||||
("Map mode", "Mod hartă"),
|
||||
("Translate mode", "Mod traducere"),
|
||||
("Use permanent password", "Folosește parola permanentă"),
|
||||
("Use both passwords", "Folosește parola unică și cea permanentă"),
|
||||
("Set permanent password", "Setează parola permanentă"),
|
||||
("Enable Remote Restart", "Activează repornirea la distanță"),
|
||||
("Allow remote restart", "Permite repornirea la distanță"),
|
||||
("Restart Remote Device", "Repornește dispozivul la distanță"),
|
||||
("Are you sure you want to restart", "Sigur vrei să repornești dispozitivul?"),
|
||||
("Restarting Remote Device", "Se repornește dispozitivul la distanță"),
|
||||
("remote_restarting_tip", "Dispozitivul este în curs de repornire. Închide acest mesaj și reconectează-te cu parola permanentă după un timp."),
|
||||
("Copied", "Copiat"),
|
||||
("Exit Fullscreen", "Ieși din modul ecran complet"),
|
||||
("Fullscreen", "Ecran complet"),
|
||||
("Mobile Actions", "Acțiuni mobile"),
|
||||
("Select Monitor", "Selectează monitor"),
|
||||
("Control Actions", "Acțiuni de control"),
|
||||
("Display Settings", "Setări afișaj"),
|
||||
("Ratio", "Raport"),
|
||||
("Image Quality", "Calitate imagine"),
|
||||
("Scroll Style", "Stil de derulare"),
|
||||
("Show Menubar", "Arată bara de meniu"),
|
||||
("Hide Menubar", "Ascunde bara de meniu"),
|
||||
("Direct Connection", "Conexiune directă"),
|
||||
("Relay Connection", "Conexiune prin retransmisie"),
|
||||
("Secure Connection", "Conexiune securizată"),
|
||||
("Insecure Connection", "Conexiune nesecurizată"),
|
||||
("Scale original", "Scală originală"),
|
||||
("Scale adaptive", "Scală adaptivă"),
|
||||
("General", "General"),
|
||||
("Security", "Securitate"),
|
||||
("Theme", "Temă"),
|
||||
("Dark Theme", "Temă întunecată"),
|
||||
("Dark", "Întunecat"),
|
||||
("Light", "Luminos"),
|
||||
("Follow System", "Urmărește sistem"),
|
||||
("Enable hardware codec", "Activează codec hardware"),
|
||||
("Unlock Security Settings", "Deblochează setări de securitate"),
|
||||
("Enable Audio", "Activează audio"),
|
||||
("Unlock Network Settings", "Deblochează setări de rețea"),
|
||||
("Server", "Server"),
|
||||
("Direct IP Access", "Acces direct IP"),
|
||||
("Proxy", "Proxy"),
|
||||
("Apply", "Aplică"),
|
||||
("Disconnect all devices?", "Vrei să deconectezi toate dispozitivele?"),
|
||||
("Clear", "Golește"),
|
||||
("Audio Input Device", "Dispozitiv de intrare audio"),
|
||||
("Deny remote access", "Interzice acces la distanță"),
|
||||
("Use IP Whitelisting", "Folosește lista de IP-uri autorizate"),
|
||||
("Network", "Rețea"),
|
||||
("Enable RDP", "Activează RDP"),
|
||||
("Pin menubar", "Fixează bara de meniu"),
|
||||
("Unpin menubar", "Detașează bara de meniu"),
|
||||
("Recording", "Înregistrare"),
|
||||
("Directory", "Director"),
|
||||
("Automatically record incoming sessions", "Înregistrează automat sesiunile viitoare"),
|
||||
("Change", "Modifică"),
|
||||
("Start session recording", "Începe înregistrare"),
|
||||
("Stop session recording", "Oprește înregistrare"),
|
||||
("Enable Recording Session", "Activează înregistrarea sesiunii"),
|
||||
("Allow recording session", "Permite înregistrarea sesiunii"),
|
||||
("Enable LAN Discovery", "Activează descoperire LAN"),
|
||||
("Deny LAN Discovery", "Interzice descoperire LAN"),
|
||||
("Write a message", "Scrie un mesaj"),
|
||||
("Prompt", "Solicită"),
|
||||
("Please wait for confirmation of UAC...", "Așteaptă confirmarea UAC..."),
|
||||
("elevated_foreground_window_tip", "Fereastra actuală a dispozitivului la distanță necesită privilegii sporite pentru a funcționa, astfel că mouse-ul și tastatura nu pot fi folosite. Poți cere utilizatorului la distanță să minimizeze fereastra actuală sau să facă clic pe butonul de sporire a privilegiilor din fereastra de gestionare a conexiunilor. Pentru a evita această problemă, recomandăm instalarea software-ului pe dispozitivul la distanță."),
|
||||
("Disconnected", "Deconectat"),
|
||||
("Other", "Altele"),
|
||||
("Confirm before closing multiple tabs", "Confirmă înainte de a închide mai multe file"),
|
||||
("Keyboard Settings", "Configurare tastatură"),
|
||||
("Full Access", "Acces total"),
|
||||
("Screen Share", "Partajare ecran"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland necesită Ubuntu 21.04 sau o versiune superioară."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland necesită o versiune superioară a distribuției Linux. Încearcă desktopul X11 sau schimbă sistemul de operare."),
|
||||
("JumpLink", "Afișează"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Partajează ecranul care urmează să fie partajat (operează din partea dispozitivului pereche)."),
|
||||
("Show RustDesk", "Afișează RustDesk"),
|
||||
("This PC", "Acest PC"),
|
||||
("or", "sau"),
|
||||
("Continue with", "Continuă cu"),
|
||||
("Elevate", "Sporește"),
|
||||
("Zoom cursor", "Cursor lupă"),
|
||||
("Accept sessions via password", "Acceptă sesiunile folosind parola"),
|
||||
("Accept sessions via click", "Acceptă sesiunile cu un clic de confirmare"),
|
||||
("Accept sessions via both", "Acceptă sesiunile folosind ambele moduri"),
|
||||
("Please wait for the remote side to accept your session request...", "Așteaptă ca solicitarea ta de conectare la distanță să fie acceptată..."),
|
||||
("One-time Password", "Parolă unică"),
|
||||
("Use one-time password", "Folosește parola unică"),
|
||||
("One-time password length", "Lungimea parolei unice"),
|
||||
("Request access to your device", "Solicită acces la dispozitivul tău"),
|
||||
("Hide connection management window", "Ascunde fereastra de gestionare a conexiunilor"),
|
||||
("hide_cm_tip", "Permite ascunderea ferestrei de gestionare doar dacă accepți începerea sesiunilor folosind parola permanentă"),
|
||||
("wayland_experiment_tip", ""),
|
||||
("Right click to select tabs", ""),
|
||||
("Skipped", ""),
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
@@ -37,11 +37,19 @@ 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", "Сделано с душой в этом безумном мире!"),
|
||||
("Privacy Statement", "Заявление о конфиденциальности"),
|
||||
("Mute", "Отключить звук"),
|
||||
("Build Date", "Дата сборки"),
|
||||
("Version", "Версия"),
|
||||
("Home", "Главная"),
|
||||
("Audio Input", "Аудиовход"),
|
||||
("Enhancements", "Улучшения"),
|
||||
("Hardware Codec", "Аппаратный кодек"),
|
||||
@@ -51,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", "Недоступно"),
|
||||
@@ -206,19 +213,18 @@ 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", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Verify", "Проверить"),
|
||||
("Remember me", "Запомнить"),
|
||||
("Trust this device", "Доверенное устройство"),
|
||||
("Verification code", "Проверочный код"),
|
||||
("verification_tip", "Обнаружено новое устройство, на зарегистрированный адрес электронной почты отправлен проверочный код. Введите его, чтобы продолжить вход в систему."),
|
||||
("Logout", "Выйти"),
|
||||
("Tags", "Метки"),
|
||||
("Search ID", "Поиск по ID"),
|
||||
("Current Wayland display server is not supported", "Текущий сервер отображения Wayland не поддерживается"),
|
||||
("whitelist_sep", "Раздельно запятой, точкой с запятой, пробелом или новой строкой"),
|
||||
("Add ID", "Добавить ID"),
|
||||
("Add Tag", "Добавить ключевое слово"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Держать в фоне службу RustDesk"),
|
||||
("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"),
|
||||
("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Подключение не разрешено"),
|
||||
("Legacy mode", "Устаревший режим"),
|
||||
("Map mode", "Режим сопоставления"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", "Добавить в адресную книгу"),
|
||||
("Group", "Группа"),
|
||||
("Search", "Поиск"),
|
||||
("Closed manually by the web console", "Закрыто вручную через веб-консоль"),
|
||||
("Closed manually by web console", "Закрыто вручную через веб-консоль"),
|
||||
("Local keyboard type", "Тип локальной клавиатуры"),
|
||||
("Select local keyboard type", "Выберите тип локальной клавиатуры"),
|
||||
("software_render_tip", "Если у вас видеокарта Nvidia и удалённое окно закрывается сразу после подключения, может помочь установка драйвера Nouveau и выбор использования программной визуализации. Потребуется перезапуск."),
|
||||
("Always use software rendering", "Использовать программную визуализацию"),
|
||||
("config_input", "Чтобы управлять удалённым рабочим столом с помощью клавиатуры, необходимо предоставить RustDesk разрешения \"Мониторинг ввода\"."),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", "Чтобы разговаривать с удалённой стороной, необходимо предоставить RustDesk разрешение \"Запись аудио\"."),
|
||||
("request_elevation_tip", "Также можно запросить повышение прав, если кто-то есть на удалённой стороне."),
|
||||
("Wait", "Ждите"),
|
||||
("Elevation Error", "Ошибка повышения прав"),
|
||||
("Ask the remote user for authentication", "Запросить аутентификацию у удалённого пользователя"),
|
||||
("Choose this if the remote account is administrator", "Выберите это, если удалённый аккаунт является администратором"),
|
||||
("Transmit the username and password of administrator", "Передать имя пользователя и пароль администратора"),
|
||||
("still_click_uac_tip", "По-прежнему требуется, чтобы удалённый пользователь нажал \"OK\" в окне UAC при запуске RustDesk."),
|
||||
("Request Elevation", "Запросить повышение"),
|
||||
("wait_accept_uac_tip", "Подождите, пока удалённый пользователь подтвердит запрос UAC."),
|
||||
("Elevate successfully", "Права повышены"),
|
||||
("uppercase", "заглавные"),
|
||||
("lowercase", "строчные"),
|
||||
("digit", "цифры"),
|
||||
("special character", "спецсимволы"),
|
||||
("length>=8", "8+ символов"),
|
||||
("Weak", "Слабый"),
|
||||
("Medium", "Средний"),
|
||||
("Strong", "Стойкий"),
|
||||
("Switch Sides", "Переключить стороны"),
|
||||
("Please confirm if you want to share your desktop?", "Подтверждаете, что хотите поделиться своим рабочим столом?"),
|
||||
("Display", "Отображение"),
|
||||
("Default View Style", "Стиль отображения по умолчанию"),
|
||||
("Default Scroll Style", "Стиль прокрутки по умолчанию"),
|
||||
("Default Image Quality", "Качество изображения по умолчанию"),
|
||||
("Default Codec", "Кодек по умолчанию"),
|
||||
("Bitrate", "Битрейт"),
|
||||
("FPS", "Частота кадров"),
|
||||
("Auto", "Авто"),
|
||||
("Other Default Options", "Другие параметры по умолчанию"),
|
||||
("Voice call", "Голосовой вызов"),
|
||||
("Text chat", "Текстовый чат"),
|
||||
("Stop voice call", "Завершить голосовой вызов"),
|
||||
("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через сервер ретрансляции. \nКроме того, если вы хотите сразу использовать сервер ретрансляции, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."),
|
||||
("Reconnect", "Переподключить"),
|
||||
("Codec", "Кодек"),
|
||||
("Resolution", "Разрешение"),
|
||||
("No transfers in progress", "Передача не осуществляется"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Stíšiť"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Zvukový vstup"),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Odhlásenie"),
|
||||
("Tags", "Štítky"),
|
||||
("Search ID", "Hľadať ID"),
|
||||
("Current Wayland display server is not supported", "Zobrazovací (display) server Wayland nie je podporovaný"),
|
||||
("whitelist_sep", "Oddelené čiarkou, bodkočiarkou, medzerou alebo koncom riadku"),
|
||||
("Add ID", "Pridať ID"),
|
||||
("Add Tag", "Pridať štítok"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", ""),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Izklopi zvok"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Avdio vhod"),
|
||||
("Enhancements", "Izboljšave"),
|
||||
("Hardware Codec", "Strojni kodek"),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Odjavi"),
|
||||
("Tags", "Oznake"),
|
||||
("Search ID", "Išči ID"),
|
||||
("Current Wayland display server is not supported", "Trenutni Wayland zaslonski strežnik ni podprt"),
|
||||
("whitelist_sep", "Naslovi ločeni z vejico, podpičjem, presledkom ali novo vrstico"),
|
||||
("Add ID", "Dodaj ID"),
|
||||
("Add Tag", "Dodaj oznako"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"),
|
||||
("Ignore Battery Optimizations", "Prezri optimizacije baterije"),
|
||||
("android_open_battery_optimizations_tip", "Če želite izklopiti to možnost, pojdite v nastavitve aplikacije RustDesk, poiščite »Baterija« in izklopite »Neomejeno«"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Povezava ni dovoljena"),
|
||||
("Legacy mode", "Stari način"),
|
||||
("Map mode", "Način preslikave"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", "Dodaj v adresar"),
|
||||
("Group", "Skupina"),
|
||||
("Search", "Iskanje"),
|
||||
("Closed manually by the web console", "Ročno zaprto iz spletne konzole"),
|
||||
("Closed manually by web console", "Ročno zaprto iz spletne konzole"),
|
||||
("Local keyboard type", "Lokalna vrsta tipkovnice"),
|
||||
("Select local keyboard type", "Izberite lokalno vrsto tipkovnice"),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Pa zë"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Inputi zërit"),
|
||||
("Enhancements", "Përmirësimet"),
|
||||
("Hardware Codec", "Kodeku Harduerik"),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Dalje"),
|
||||
("Tags", "Tage"),
|
||||
("Search ID", "Kerko ID"),
|
||||
("Current Wayland display server is not supported", "Serveri aktual i ekranit Wayland nuk mbështetet"),
|
||||
("whitelist_sep", "Të ndara me presje, pikëpresje, hapësira ose rresht të ri"),
|
||||
("Add ID", "Shto ID"),
|
||||
("Add Tag", "Shto Tag"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Mbaje shërbimin e sfondit të RustDesk"),
|
||||
("Ignore Battery Optimizations", "Injoro optimizimet e baterisë"),
|
||||
("android_open_battery_optimizations_tip", "Nëse dëshironi ta çaktivizoni këtë veçori, ju lutemi shkoni te faqja tjetër e cilësimeve të aplikacionit RustDesk, gjeni dhe shtypni [Batteri], hiqni zgjedhjen [Te pakufizuara]"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Lidhja nuk lejohet"),
|
||||
("Legacy mode", "Modaliteti i trashëgimisë"),
|
||||
("Map mode", "Modaliteti i hartës"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Utišaj"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Audio ulaz"),
|
||||
("Enhancements", "Proširenja"),
|
||||
("Hardware Codec", "Hardverski kodek"),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Odjava"),
|
||||
("Tags", "Oznake"),
|
||||
("Search ID", "Traži ID"),
|
||||
("Current Wayland display server is not supported", "Tekući Wazland server za prikaz nije podržan"),
|
||||
("whitelist_sep", "Odvojeno zarezima, tačka zarezima, praznim mestima ili novim redovima"),
|
||||
("Add ID", "Dodaj ID"),
|
||||
("Add Tag", "Dodaj oznaku"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Zadrži RustDesk kao pozadinski servis"),
|
||||
("Ignore Battery Optimizations", "Zanemari optimizacije baterije"),
|
||||
("android_open_battery_optimizations_tip", "Ako želite da onemogućite ovu funkciju, molimo idite na sledeću stranicu za podešavanje RustDesk aplikacije, pronađite i uđite u [Battery], isključite [Unrestricted]"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Konekcija nije dozvoljena"),
|
||||
("Legacy mode", "Zastareli mod"),
|
||||
("Map mode", "Mod mapiranja"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", "Dodaj u adresar"),
|
||||
("Group", "Grupa"),
|
||||
("Search", "Pretraga"),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Tyst"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Ljud input"),
|
||||
("Enhancements", "Förbättringar"),
|
||||
("Hardware Codec", "Hårdvarucodec"),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Logga ut"),
|
||||
("Tags", "Taggar"),
|
||||
("Search ID", "Sök ID"),
|
||||
("Current Wayland display server is not supported", "Nuvarande Wayland displayserver stöds inte"),
|
||||
("whitelist_sep", "Separerat av ett comma, semikolon, mellanslag eller ny linje"),
|
||||
("Add ID", "Lägg till ID"),
|
||||
("Add Tag", "Lägg till Tagg"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Behåll RustDesk i bakgrunden"),
|
||||
("Ignore Battery Optimizations", "Ignorera batterioptimering"),
|
||||
("android_open_battery_optimizations_tip", "Om du vill stänga av denna funktion, gå till nästa RustDesk programs inställningar, hitta [Batteri], Checka ur [Obegränsad]"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Anslutning ej tillåten"),
|
||||
("Legacy mode", "Legacy mode"),
|
||||
("Map mode", "Kartläge"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", ""),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", ""),
|
||||
("Enhancements", ""),
|
||||
("Hardware Codec", ""),
|
||||
@@ -51,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", ""),
|
||||
@@ -206,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", ""),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", ""),
|
||||
("Tags", ""),
|
||||
("Search ID", ""),
|
||||
("Current Wayland display server is not supported", ""),
|
||||
("whitelist_sep", ""),
|
||||
("Add ID", ""),
|
||||
("Add Tag", ""),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", ""),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", "ทำด้วยใจ ในโลกใบนี้ที่ยุ่งเหยิง!"),
|
||||
("Privacy Statement", "คำแถลงเกี่ยวกับความเป็นส่วนตัว"),
|
||||
("Mute", "ปิดเสียง"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "ออดิโออินพุท"),
|
||||
("Enhancements", "การปรับปรุง"),
|
||||
("Hardware Codec", "ฮาร์ดแวร์ codec"),
|
||||
@@ -51,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", "ไม่พร้อมใช้งาน"),
|
||||
@@ -206,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", "เข้าสู่ระบบ"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "ออกจากระบบ"),
|
||||
("Tags", "แท็ก"),
|
||||
("Search ID", "ค้นหา ID"),
|
||||
("Current Wayland display server is not supported", "เซิร์ฟเวอร์การแสดงผล Wayland ปัจจุบันไม่รองรับ"),
|
||||
("whitelist_sep", "คั่นโดยเครื่องหมาย comma semicolon เว้นวรรค หรือ ขึ้นบรรทัดใหม่"),
|
||||
("Add ID", "เพิ่ม ID"),
|
||||
("Add Tag", "เพิ่มแท็ก"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"),
|
||||
("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"),
|
||||
("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", "เพิ่มไปยังสมุดรายชื่อ"),
|
||||
("Group", "กลุ่ม"),
|
||||
("Search", "ค้นหา"),
|
||||
("Closed manually by the web console", "ถูกปิดโดยเว็บคอนโซล"),
|
||||
("Closed manually by web console", "ถูกปิดโดยเว็บคอนโซล"),
|
||||
("Local keyboard type", "ประเภทคีย์บอร์ด"),
|
||||
("Select local keyboard type", "เลือกประเภทคีย์บอร์ด"),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Sustur"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Ses Girişi"),
|
||||
("Enhancements", "Geliştirmeler"),
|
||||
("Hardware Codec", "Donanımsal Codec"),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Çıkış yap"),
|
||||
("Tags", "Etiketler"),
|
||||
("Search ID", "ID Arama"),
|
||||
("Current Wayland display server is not supported", "Mevcut Wayland görüntüleme sunucusu desteklenmiyor"),
|
||||
("whitelist_sep", "Virgül, noktalı virgül, boşluk veya yeni satır ile ayrılmış"),
|
||||
("Add ID", "ID Ekle"),
|
||||
("Add Tag", "Etiket Ekle"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"),
|
||||
("Ignore Battery Optimizations", "Pil Optimizasyonlarını Yoksay"),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "bağlantıya izin verilmedi"),
|
||||
("Legacy mode", "Eski mod"),
|
||||
("Map mode", "Haritalama modu"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Privacy Statement", "隱私聲明"),
|
||||
("Mute", "靜音"),
|
||||
("Build Date", "建構日期"),
|
||||
("Version", "版本"),
|
||||
("Home", "主頁"),
|
||||
("Audio Input", "音訊輸入"),
|
||||
("Enhancements", "增強功能"),
|
||||
("Hardware Codec", "硬件編解碼"),
|
||||
@@ -51,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", "無法使用"),
|
||||
@@ -206,19 +213,18 @@ 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", "登入"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Verify", "驗證"),
|
||||
("Remember me", "記住我"),
|
||||
("Trust this device", "信任此設備"),
|
||||
("Verification code", "驗證碼"),
|
||||
("verification_tip", "檢測到新設備登錄,已向註冊郵箱發送了登入驗證碼,請輸入驗證碼繼續登錄"),
|
||||
("Logout", "登出"),
|
||||
("Tags", "標籤"),
|
||||
("Search ID", "搜尋 ID"),
|
||||
("Current Wayland display server is not supported", "目前不支援 Wayland 顯示伺服器"),
|
||||
("whitelist_sep", "使用逗號、分號、空白,或是換行來分隔"),
|
||||
("Add ID", "新增 ID"),
|
||||
("Add Tag", "新增標籤"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "保持RustDesk後台服務"),
|
||||
("Ignore Battery Optimizations", "忽略電池優化"),
|
||||
("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的RustDesk應用設置頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "對方不允許連接"),
|
||||
("Legacy mode", "傳統模式"),
|
||||
("Map mode", "1:1傳輸"),
|
||||
@@ -385,12 +393,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland 需要更高版本的 linux 發行版。 請嘗試 X11 桌面或更改您的操作系統。"),
|
||||
("JumpLink", "查看"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的畫面(在對端操作)。"),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
("Show RustDesk", "顯示 RustDesk"),
|
||||
("This PC", "此電腦"),
|
||||
("or", "或"),
|
||||
("Continue with", "使用"),
|
||||
("Elevate", "提權"),
|
||||
("Zoom cursor", ""),
|
||||
("Zoom cursor", "縮放游標"),
|
||||
("Accept sessions via password", "只允許密碼訪問"),
|
||||
("Accept sessions via click", "只允許點擊訪問"),
|
||||
("Accept sessions via both", "允許密碼或點擊訪問"),
|
||||
@@ -401,17 +409,55 @@ 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_experiment_tip", "Wayland 支持處於實驗階段,如果你需要使用無人值守訪問,請使用 X11。"),
|
||||
("Right click to select tabs", "右鍵選擇選項卡"),
|
||||
("Skipped", ""),
|
||||
("Skipped", "已略過"),
|
||||
("Add to Address Book", "添加到地址簿"),
|
||||
("Group", "小組"),
|
||||
("Search", "搜索"),
|
||||
("Closed manually by the web console", "被web控制台手動關閉"),
|
||||
("Closed manually by web console", "被web控制台手動關閉"),
|
||||
("Local keyboard type", "本地鍵盤類型"),
|
||||
("Select local keyboard type", "請選擇本地鍵盤類型"),
|
||||
("software_render_tip", "如果你使用英偉達顯卡, 並且遠程窗口在會話建立後會立刻關閉, 那麼安裝nouveau驅動並且選擇使用軟件渲染可能會有幫助。重啟軟件後生效。"),
|
||||
("Always use software rendering", "使用軟件渲染"),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_input", "為了能夠通過鍵盤控制遠程桌面, 請給予 RustDesk \"輸入監控\" 權限。"),
|
||||
("config_microphone", "為了支持通過麥克風進行音訊傳輸,請給予 RustDesk \"錄音\"權限。"),
|
||||
("request_elevation_tip", "如果對面有人, 也可以請求提升權限。"),
|
||||
("Wait", "等待"),
|
||||
("Elevation Error", "提權失敗"),
|
||||
("Ask the remote user for authentication", "請求遠端用戶授權"),
|
||||
("Choose this if the remote account is administrator", "當對面電腦是管理員賬號時選擇該選項"),
|
||||
("Transmit the username and password of administrator", "發送管理員賬號的用戶名密碼"),
|
||||
("still_click_uac_tip", "依然需要被控端用戶在UAC窗口點擊確認。"),
|
||||
("Request Elevation", "請求提權"),
|
||||
("wait_accept_uac_tip", "請等待遠端用戶確認UAC對話框。"),
|
||||
("Elevate successfully", "提權成功"),
|
||||
("uppercase", "大寫字母"),
|
||||
("lowercase", "小寫字母"),
|
||||
("digit", "數字"),
|
||||
("special character", "特殊字符"),
|
||||
("length>=8", "長度不小於8"),
|
||||
("Weak", "弱"),
|
||||
("Medium", "中"),
|
||||
("Strong", "強"),
|
||||
("Switch Sides", "反轉訪問方向"),
|
||||
("Please confirm if you want to share your desktop?", "請確認是否要讓對方訪問你的桌面?"),
|
||||
("Display", "顯示"),
|
||||
("Default View Style", "默認顯示方式"),
|
||||
("Default Scroll Style", "默認滾動方式"),
|
||||
("Default Image Quality", "默認圖像質量"),
|
||||
("Default Codec", "默認編解碼"),
|
||||
("Bitrate", "波特率"),
|
||||
("FPS", "幀率"),
|
||||
("Auto", "自動"),
|
||||
("Other Default Options", "其它默認選項"),
|
||||
("Voice call", "語音通話"),
|
||||
("Text chat", "文字聊天"),
|
||||
("Stop voice call", "停止語音聊天"),
|
||||
("relay_hint_tip", "可能無法直連,可以嘗試中繼連接。 \n另外,如果想直接使用中繼連接,可以在ID後面添加/r,或者在卡片選項裡選擇強制走中繼連接。"),
|
||||
("Reconnect", "重連"),
|
||||
("Codec", "編解碼"),
|
||||
("Resolution", "分辨率"),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", "Створено з душею в цьому хаотичному світі!"),
|
||||
("Privacy Statement", "Декларація про конфіденційність"),
|
||||
("Mute", "Вимкнути звук"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Аудіовхід"),
|
||||
("Enhancements", "Покращення"),
|
||||
("Hardware Codec", "Апаратний кодек"),
|
||||
@@ -51,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", "Недоступно"),
|
||||
@@ -206,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", "Увійти"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Вийти"),
|
||||
("Tags", "Ключові слова"),
|
||||
("Search ID", "Пошук за ID"),
|
||||
("Current Wayland display server is not supported", "Поточний графічний сервер Wayland не підтримується"),
|
||||
("whitelist_sep", "Розділені комою, крапкою з комою, пробілом або новим рядком"),
|
||||
("Add ID", "Додати ID"),
|
||||
("Add Tag", "Додати ключове слово"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Зберегти фонову службу RustDesk"),
|
||||
("Ignore Battery Optimizations", "Ігнорувати оптимізацію батареї"),
|
||||
("android_open_battery_optimizations_tip", "Перейдіть на наступну сторінку налаштувань"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Підключення не дозволено"),
|
||||
("Legacy mode", "Застарілий режим"),
|
||||
("Map mode", "Режим карти"),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", "Додати IP до Адресної книги"),
|
||||
("Group", "Група"),
|
||||
("Search", "Пошук"),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,19 @@ 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", ""),
|
||||
("Privacy Statement", ""),
|
||||
("Mute", "Tắt tiếng"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Audio Input", "Đầu vào âm thanh"),
|
||||
("Enhancements", "Các tiện itchs"),
|
||||
("Hardware Codec", "Codec phần cứng"),
|
||||
@@ -51,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"),
|
||||
@@ -206,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"),
|
||||
@@ -218,7 +225,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "Đăng xuất"),
|
||||
("Tags", "Tags"),
|
||||
("Search ID", "Tìm ID"),
|
||||
("Current Wayland display server is not supported", "Máy chủ hình ảnh Wayland hiện không đuợc hỗ trợ"),
|
||||
("whitelist_sep", "Đuợc cách nhau bởi dấu phẩy, dấu chấm phẩy, dấu cách hay dòng mới"),
|
||||
("Add ID", "Thêm ID"),
|
||||
("Add Tag", "Thêm Tag"),
|
||||
@@ -306,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Giữ dịch vụ nền RustDesk"),
|
||||
("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"),
|
||||
("android_open_battery_optimizations_tip", "Nếu bạn muốn tắt tính năng này, vui lòng chuyển đến trang cài đặt ứng dụng RustDesk tiếp theo, tìm và nhập [Pin], Bỏ chọn [Không hạn chế]"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Kết nối không đuợc phép"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
@@ -407,11 +415,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to Address Book", ""),
|
||||
("Group", ""),
|
||||
("Search", ""),
|
||||
("Closed manually by the web console", ""),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
].iter().cloned().collect();
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
17
src/main.rs
17
src/main.rs
@@ -1,10 +1,11 @@
|
||||
// Specify the Windows subsystem to eliminate console window.
|
||||
// Requires Rust 1.18.
|
||||
//#![windows_subsystem = "windows"]
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
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 +17,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;
|
||||
@@ -91,6 +97,7 @@ fn main() {
|
||||
let token = LocalConfig::get_option("access_token");
|
||||
cli::connect_test(p, key, token);
|
||||
} else if let Some(p) = matches.value_of("server") {
|
||||
log::info!("id={}", hbb_common::config::Config::get_id());
|
||||
crate::start_server(true);
|
||||
}
|
||||
common::global_clean();
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use std::{ffi::c_void, rc::Rc};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSApplication, NSApplicationActivationPolicy::*, NSMenu, NSMenuItem},
|
||||
base::{id, nil, YES},
|
||||
foundation::{NSAutoreleasePool, NSString},
|
||||
};
|
||||
use objc::runtime::Class;
|
||||
use objc::{
|
||||
class,
|
||||
declare::ClassDecl,
|
||||
@@ -12,7 +15,8 @@ use objc::{
|
||||
sel, sel_impl,
|
||||
};
|
||||
use sciter::{make_args, Host};
|
||||
use std::{ffi::c_void, rc::Rc};
|
||||
|
||||
use hbb_common::log;
|
||||
|
||||
static APP_HANDLER_IVAR: &str = "GoDeskAppHandler";
|
||||
|
||||
@@ -22,7 +26,7 @@ const SHOW_SETTINGS_TAG: u32 = 2;
|
||||
const RUN_ME_TAG: u32 = 3;
|
||||
const AWAKE: u32 = 4;
|
||||
|
||||
trait AppHandler {
|
||||
pub trait AppHandler {
|
||||
fn command(&mut self, cmd: u32);
|
||||
}
|
||||
|
||||
@@ -42,7 +46,7 @@ impl DelegateState {
|
||||
}
|
||||
}
|
||||
|
||||
static mut LAUCHED: bool = false;
|
||||
static mut LAUNCHED: bool = false;
|
||||
|
||||
impl AppHandler for Rc<Host> {
|
||||
fn command(&mut self, cmd: u32) {
|
||||
@@ -59,9 +63,12 @@ impl AppHandler for Rc<Host> {
|
||||
}
|
||||
|
||||
// https://github.com/xi-editor/druid/blob/master/druid-shell/src/platform/mac/application.rs
|
||||
unsafe fn set_delegate(handler: Option<Box<dyn AppHandler>>) {
|
||||
let mut decl =
|
||||
ClassDecl::new("AppDelegate", class!(NSObject)).expect("App Delegate definition failed");
|
||||
pub unsafe fn set_delegate(handler: Option<Box<dyn AppHandler>>) {
|
||||
let decl = ClassDecl::new("AppDelegate", class!(NSObject));
|
||||
if decl.is_none() {
|
||||
return;
|
||||
}
|
||||
let mut decl = decl.unwrap();
|
||||
decl.add_ivar::<*mut c_void>(APP_HANDLER_IVAR);
|
||||
|
||||
decl.add_method(
|
||||
@@ -98,18 +105,30 @@ 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 "C" fn(&Object, Sel, u64, u64),
|
||||
);
|
||||
let decl = decl.register();
|
||||
let delegate: id = msg_send![decl, alloc];
|
||||
let () = msg_send![delegate, init];
|
||||
let state = DelegateState { handler };
|
||||
let handler_ptr = Box::into_raw(Box::new(state));
|
||||
(*delegate).set_ivar(APP_HANDLER_IVAR, handler_ptr as *mut c_void);
|
||||
// Set the url scheme handler
|
||||
let cls = Class::get("NSAppleEventManager").unwrap();
|
||||
let manager: *mut Object = msg_send![cls, sharedAppleEventManager];
|
||||
let _: () = msg_send![manager,
|
||||
setEventHandler: delegate
|
||||
andSelector: sel!(handleEvent:withReplyEvent:)
|
||||
forEventClass: fruitbasket::kInternetEventClass
|
||||
andEventID: fruitbasket::kAEGetURL];
|
||||
let () = msg_send![NSApp(), setDelegate: delegate];
|
||||
}
|
||||
|
||||
extern "C" fn application_did_finish_launching(_this: &mut Object, _: Sel, _notification: id) {
|
||||
unsafe {
|
||||
LAUCHED = true;
|
||||
LAUNCHED = true;
|
||||
}
|
||||
unsafe {
|
||||
let () = msg_send![NSApp(), activateIgnoringOtherApps: YES];
|
||||
@@ -122,13 +141,10 @@ extern "C" fn application_should_handle_open_untitled_file(
|
||||
_sender: id,
|
||||
) -> BOOL {
|
||||
unsafe {
|
||||
if !LAUCHED {
|
||||
if !LAUNCHED {
|
||||
return YES;
|
||||
}
|
||||
hbb_common::log::debug!("icon clicked on finder");
|
||||
if std::env::args().nth(1) == Some("--server".to_owned()) {
|
||||
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);
|
||||
@@ -167,6 +183,13 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
|
||||
}
|
||||
}
|
||||
|
||||
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 || crate::handle_url_scheme(url));
|
||||
}
|
||||
|
||||
unsafe fn make_menu_item(title: &str, key: &str, tag: u32) -> *mut Object {
|
||||
let title = NSString::alloc(nil).init_str(title);
|
||||
let action = sel!(handleMenuItem:);
|
||||
@@ -227,30 +250,3 @@ pub fn show_dock() {
|
||||
NSApp().setActivationPolicy_(NSApplicationActivationPolicyRegular);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_tray() {
|
||||
unsafe {
|
||||
set_delegate(None);
|
||||
}
|
||||
crate::tray::make_tray();
|
||||
}
|
||||
|
||||
pub fn check_main_window() {
|
||||
use sysinfo::{ProcessExt, System, SystemExt};
|
||||
let mut sys = System::new();
|
||||
sys.refresh_processes();
|
||||
let app = format!("/Applications/{}.app", crate::get_app_name());
|
||||
let my_uid = sys
|
||||
.process((std::process::id() as i32).into())
|
||||
.map(|x| x.user_id())
|
||||
.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;
|
||||
}
|
||||
}
|
||||
std::process::Command::new("open")
|
||||
.args(["-n", &app])
|
||||
.status()
|
||||
.ok();
|
||||
}
|
||||
@@ -1,16 +1,24 @@
|
||||
use super::{CursorData, ResultType};
|
||||
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,
|
||||
libc::{c_char, c_int, c_long, c_void},
|
||||
log,
|
||||
message_proto::Resolution,
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
process::{Child, Command},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use xrandr_parser::Parser;
|
||||
|
||||
type Xdo = *const c_void;
|
||||
|
||||
@@ -54,8 +62,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)> {
|
||||
@@ -162,10 +170,29 @@ fn start_uinput_service() {
|
||||
});
|
||||
}
|
||||
|
||||
fn stop_server(server: &mut Option<std::process::Child>) {
|
||||
#[inline]
|
||||
fn try_start_server_(user: Option<(String, String)>) -> ResultType<Option<Child>> {
|
||||
if user.is_some() {
|
||||
run_as_user(vec!["--server"], user)
|
||||
} else {
|
||||
Ok(Some(crate::run_me(vec!["--server"])?))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn start_server(user: Option<(String, String)>, server: &mut Option<Child>) {
|
||||
match try_start_server_(user) {
|
||||
Ok(ps) => *server = ps,
|
||||
Err(err) => {
|
||||
log::error!("Failed to start server: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_server(server: &mut Option<Child>) {
|
||||
if let Some(mut ps) = server.take() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
std::thread::sleep(Duration::from_millis(30));
|
||||
match ps.try_wait() {
|
||||
Ok(Some(_status)) => {}
|
||||
Ok(None) => {
|
||||
@@ -182,7 +209,7 @@ fn set_x11_env(uid: &str) {
|
||||
let mut auth = get_env_tries("XAUTHORITY", uid, 10);
|
||||
// auth is another user's when uid = 0, https://github.com/rustdesk/rustdesk/issues/2468
|
||||
if auth.is_empty() || uid == "0" {
|
||||
auth = if std::path::Path::new(&gdm).exists() {
|
||||
auth = if Path::new(&gdm).exists() {
|
||||
gdm
|
||||
} else {
|
||||
let username = get_active_username();
|
||||
@@ -190,7 +217,7 @@ fn set_x11_env(uid: &str) {
|
||||
format!("/{}/.Xauthority", username)
|
||||
} else {
|
||||
let tmp = format!("/home/{}/.Xauthority", username);
|
||||
if std::path::Path::new(&tmp).exists() {
|
||||
if Path::new(&tmp).exists() {
|
||||
tmp
|
||||
} else {
|
||||
format!("/var/lib/{}/.Xauthority", username)
|
||||
@@ -223,8 +250,8 @@ fn should_start_server(
|
||||
uid: &mut String,
|
||||
cur_uid: String,
|
||||
cm0: &mut bool,
|
||||
last_restart: &mut std::time::Instant,
|
||||
server: &mut Option<std::process::Child>,
|
||||
last_restart: &mut Instant,
|
||||
server: &mut Option<Child>,
|
||||
) -> bool {
|
||||
let cm = get_cm();
|
||||
let mut start_new = false;
|
||||
@@ -235,8 +262,8 @@ fn should_start_server(
|
||||
}
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
*last_restart = std::time::Instant::now();
|
||||
std::thread::sleep(Duration::from_millis(30));
|
||||
*last_restart = Instant::now();
|
||||
}
|
||||
} else if !cm
|
||||
&& ((*cm0 && last_restart.elapsed().as_secs() > 60)
|
||||
@@ -247,8 +274,8 @@ fn should_start_server(
|
||||
// and x server get displays failure issue
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
*last_restart = std::time::Instant::now();
|
||||
std::thread::sleep(Duration::from_millis(30));
|
||||
*last_restart = Instant::now();
|
||||
log::info!("restart server");
|
||||
}
|
||||
}
|
||||
@@ -267,6 +294,13 @@ fn should_start_server(
|
||||
start_new
|
||||
}
|
||||
|
||||
// to-do: stop_server(&mut user_server); may not stop child correctly
|
||||
// stop_rustdesk_servers() is just a temp solution here.
|
||||
fn force_stop_server() {
|
||||
stop_rustdesk_servers();
|
||||
std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
}
|
||||
|
||||
pub fn start_os_service() {
|
||||
stop_rustdesk_servers();
|
||||
start_uinput_service();
|
||||
@@ -274,8 +308,8 @@ pub fn start_os_service() {
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
let mut uid = "".to_owned();
|
||||
let mut server: Option<std::process::Child> = None;
|
||||
let mut user_server: Option<std::process::Child> = None;
|
||||
let mut server: Option<Child> = None;
|
||||
let mut user_server: Option<Child> = None;
|
||||
if let Err(err) = ctrlc::set_handler(move || {
|
||||
r.store(false, Ordering::SeqCst);
|
||||
}) {
|
||||
@@ -283,12 +317,13 @@ pub fn start_os_service() {
|
||||
}
|
||||
|
||||
let mut cm0 = false;
|
||||
let mut last_restart = std::time::Instant::now();
|
||||
let mut last_restart = Instant::now();
|
||||
while running.load(Ordering::SeqCst) {
|
||||
let (cur_uid, cur_user) = get_active_user_id_name();
|
||||
let is_wayland = current_is_wayland();
|
||||
|
||||
if cur_user == "root" || !is_wayland {
|
||||
// try kill subprocess "--server"
|
||||
stop_server(&mut user_server);
|
||||
// try start subprocess "--server"
|
||||
if should_start_server(
|
||||
@@ -299,16 +334,8 @@ pub fn start_os_service() {
|
||||
&mut last_restart,
|
||||
&mut server,
|
||||
) {
|
||||
// to-do: stop_server(&mut user_server); may not stop child correctly
|
||||
// stop_rustdesk_servers() is just a temp solution here.
|
||||
stop_rustdesk_servers();
|
||||
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
match crate::run_me(vec!["--server"]) {
|
||||
Ok(ps) => server = Some(ps),
|
||||
Err(err) => {
|
||||
log::error!("Failed to start server: {}", err);
|
||||
}
|
||||
}
|
||||
force_stop_server();
|
||||
start_server(None, &mut server);
|
||||
}
|
||||
} else if cur_user != "" {
|
||||
if cur_user != "gdm" {
|
||||
@@ -324,23 +351,16 @@ pub fn start_os_service() {
|
||||
&mut last_restart,
|
||||
&mut user_server,
|
||||
) {
|
||||
stop_rustdesk_servers();
|
||||
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
match run_as_user(vec!["--server"], Some((cur_uid, cur_user))) {
|
||||
Ok(ps) => user_server = ps,
|
||||
Err(err) => {
|
||||
log::error!("Failed to start server: {}", err);
|
||||
}
|
||||
}
|
||||
force_stop_server();
|
||||
start_server(Some((cur_uid, cur_user)), &mut user_server);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stop_rustdesk_servers();
|
||||
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
force_stop_server();
|
||||
stop_server(&mut user_server);
|
||||
stop_server(&mut server);
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
}
|
||||
|
||||
if let Some(ps) = user_server.take().as_mut() {
|
||||
@@ -362,7 +382,7 @@ pub fn get_active_userid() -> String {
|
||||
}
|
||||
|
||||
fn get_cm() -> bool {
|
||||
if let Ok(output) = std::process::Command::new("ps").args(vec!["aux"]).output() {
|
||||
if let Ok(output) = Command::new("ps").args(vec!["aux"]).output() {
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
if line.contains(&format!(
|
||||
"{} --cm",
|
||||
@@ -380,7 +400,7 @@ fn get_cm() -> bool {
|
||||
fn get_display() -> String {
|
||||
let user = get_active_username();
|
||||
log::debug!("w {}", &user);
|
||||
if let Ok(output) = std::process::Command::new("w").arg(&user).output() {
|
||||
if let Ok(output) = Command::new("w").arg(&user).output() {
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
log::debug!(" {}", line);
|
||||
let mut iter = line.split_whitespace();
|
||||
@@ -395,7 +415,7 @@ fn get_display() -> String {
|
||||
// above not work for gdm user
|
||||
log::debug!("ls -l /tmp/.X11-unix/");
|
||||
let mut last = "".to_owned();
|
||||
if let Ok(output) = std::process::Command::new("ls")
|
||||
if let Ok(output) = Command::new("ls")
|
||||
.args(vec!["-l", "/tmp/.X11-unix/"])
|
||||
.output()
|
||||
{
|
||||
@@ -426,104 +446,11 @@ pub fn is_login_wayland() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fix_login_wayland() {
|
||||
let mut file = "/etc/gdm3/custom.conf".to_owned();
|
||||
if !std::path::Path::new(&file).exists() {
|
||||
file = "/etc/gdm/custom.conf".to_owned();
|
||||
}
|
||||
match std::process::Command::new("pkexec")
|
||||
.args(vec![
|
||||
"sed",
|
||||
"-i",
|
||||
"s/#WaylandEnable=false/WaylandEnable=false/g",
|
||||
&file,
|
||||
])
|
||||
.output()
|
||||
{
|
||||
Ok(x) => {
|
||||
let x = String::from_utf8_lossy(&x.stderr);
|
||||
if !x.is_empty() {
|
||||
log::error!("fix_login_wayland failed: {}", x);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("fix_login_wayland failed: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_is_wayland() -> bool {
|
||||
let dtype = get_display_server();
|
||||
return "wayland" == dtype && unsafe { UNMODIFIED };
|
||||
}
|
||||
|
||||
pub fn modify_default_login() -> String {
|
||||
let dsession = std::env::var("DESKTOP_SESSION").unwrap();
|
||||
let user_name = std::env::var("USERNAME").unwrap();
|
||||
if let Ok(x) = run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION}-xorg.desktop".to_owned()) {
|
||||
if x.trim_end().to_string() != "" {
|
||||
match std::process::Command::new("pkexec")
|
||||
.args(vec![
|
||||
"sed",
|
||||
"-i",
|
||||
&format!("s/={0}$/={0}-xorg/g", &dsession),
|
||||
&format!("/var/lib/AccountsService/users/{}", &user_name),
|
||||
])
|
||||
.output()
|
||||
{
|
||||
Ok(x) => {
|
||||
let x = String::from_utf8_lossy(&x.stderr);
|
||||
if !x.is_empty() {
|
||||
log::error!("modify_default_login failed: {}", x);
|
||||
return "Fix failed! Please re-login with X server manually".to_owned();
|
||||
} else {
|
||||
unsafe {
|
||||
UNMODIFIED = false;
|
||||
}
|
||||
return "".to_owned();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("modify_default_login failed: {}", err);
|
||||
return "Fix failed! Please re-login with X server manually".to_owned();
|
||||
}
|
||||
}
|
||||
} else if let Ok(z) =
|
||||
run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION:0:-8}.desktop".to_owned())
|
||||
{
|
||||
if z.trim_end().to_string() != "" {
|
||||
match std::process::Command::new("pkexec")
|
||||
.args(vec![
|
||||
"sed",
|
||||
"-i",
|
||||
&format!("s/={}$/={}/g", &dsession, &dsession[..dsession.len() - 8]),
|
||||
&format!("/var/lib/AccountsService/users/{}", &user_name),
|
||||
])
|
||||
.output()
|
||||
{
|
||||
Ok(x) => {
|
||||
let x = String::from_utf8_lossy(&x.stderr);
|
||||
if !x.is_empty() {
|
||||
log::error!("modify_default_login failed: {}", x);
|
||||
return "Fix failed! Please re-login with X server manually".to_owned();
|
||||
} else {
|
||||
unsafe {
|
||||
UNMODIFIED = false;
|
||||
}
|
||||
return "".to_owned();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("modify_default_login failed: {}", err);
|
||||
return "Fix failed! Please re-login with X server manually".to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "Fix failed! Please re-login with X server manually".to_owned();
|
||||
}
|
||||
|
||||
// to-do: test the other display manager
|
||||
fn _get_display_manager() -> String {
|
||||
if let Ok(x) = std::fs::read_to_string("/etc/X11/default-display-manager") {
|
||||
@@ -567,10 +494,7 @@ fn is_opensuse() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn run_as_user(
|
||||
arg: Vec<&str>,
|
||||
user: Option<(String, String)>,
|
||||
) -> ResultType<Option<std::process::Child>> {
|
||||
pub fn run_as_user(arg: Vec<&str>, user: Option<(String, String)>) -> ResultType<Option<Child>> {
|
||||
let (uid, username) = match user {
|
||||
Some(id_name) => id_name,
|
||||
None => get_active_user_id_name(),
|
||||
@@ -584,7 +508,7 @@ pub fn run_as_user(
|
||||
args.insert(0, "-E");
|
||||
}
|
||||
|
||||
let task = std::process::Command::new("sudo").args(args).spawn()?;
|
||||
let task = Command::new("sudo").args(args).spawn()?;
|
||||
Ok(Some(task))
|
||||
}
|
||||
|
||||
@@ -627,11 +551,26 @@ pub fn get_pa_sources() -> Vec<(String, String)> {
|
||||
out
|
||||
}
|
||||
|
||||
pub fn get_default_pa_source() -> Option<(String, String)> {
|
||||
use pulsectl::controllers::*;
|
||||
match SourceController::create() {
|
||||
Ok(mut handler) => {
|
||||
if let Ok(dev) = handler.get_default_device() {
|
||||
return Some((
|
||||
dev.name.unwrap_or("".to_owned()),
|
||||
dev.description.unwrap_or("".to_owned()),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to get_pa_source: {:?}", err);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn lock_screen() {
|
||||
std::process::Command::new("xdg-screensaver")
|
||||
.arg("lock")
|
||||
.spawn()
|
||||
.ok();
|
||||
Command::new("xdg-screensaver").arg("lock").spawn().ok();
|
||||
}
|
||||
|
||||
pub fn toggle_blank_screen(_v: bool) {
|
||||
@@ -652,7 +591,7 @@ fn get_env_tries(name: &str, uid: &str, n: usize) -> String {
|
||||
if !x.is_empty() {
|
||||
return x;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(300));
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
}
|
||||
"".to_owned()
|
||||
}
|
||||
@@ -679,12 +618,12 @@ pub fn quit_gui() {
|
||||
pub fn check_super_user_permission() -> ResultType<bool> {
|
||||
let file = "/usr/share/rustdesk/files/polkit";
|
||||
let arg;
|
||||
if std::path::Path::new(file).is_file() {
|
||||
if Path::new(file).is_file() {
|
||||
arg = file;
|
||||
} else {
|
||||
arg = "echo";
|
||||
}
|
||||
let status = std::process::Command::new("pkexec").arg(arg).status()?;
|
||||
let status = Command::new("pkexec").arg(arg).status()?;
|
||||
Ok(status.success() && status.code() == Some(0))
|
||||
}
|
||||
|
||||
@@ -707,94 +646,65 @@ pub fn get_double_click_time() -> u32 {
|
||||
unsafe {
|
||||
let mut double_click_time = 0u32;
|
||||
let property = std::ffi::CString::new("gtk-double-click-time").unwrap();
|
||||
let setings = gtk_settings_get_default();
|
||||
let settings = gtk_settings_get_default();
|
||||
g_object_get(
|
||||
setings,
|
||||
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()
|
||||
);
|
||||
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<()> {
|
||||
Command::new("xrandr")
|
||||
.args(vec![
|
||||
"--output",
|
||||
name,
|
||||
"--mode",
|
||||
&format!("{}x{}", width, height),
|
||||
])
|
||||
.spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <IOKit/hidsystem/IOHIDLib.h>
|
||||
#include <Security/Authorization.h>
|
||||
#include <Security/AuthorizationTags.h>
|
||||
|
||||
|
||||
// https://github.com/codebytere/node-mac-permissions/blob/main/permissions.mm
|
||||
|
||||
@@ -34,3 +37,161 @@ extern "C" bool InputMonitoringAuthStatus(bool prompt) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" bool MacCheckAdminAuthorization() {
|
||||
AuthorizationRef authRef;
|
||||
OSStatus status;
|
||||
|
||||
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
|
||||
kAuthorizationFlagDefaults, &authRef);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
printf("Failed to create AuthorizationRef\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthorizationItem authItem = {kAuthorizationRightExecute, 0, NULL, 0};
|
||||
AuthorizationRights authRights = {1, &authItem};
|
||||
AuthorizationFlags flags = kAuthorizationFlagDefaults |
|
||||
kAuthorizationFlagInteractionAllowed |
|
||||
kAuthorizationFlagPreAuthorize |
|
||||
kAuthorizationFlagExtendRights;
|
||||
status = AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
printf("Failed to authorize\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthorizationFree(authRef, kAuthorizationFlagDefaults);
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" float BackingScaleFactor() {
|
||||
NSScreen* s = [NSScreen mainScreen];
|
||||
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
|
||||
|
||||
size_t bitDepth(CGDisplayModeRef mode) {
|
||||
size_t depth = 0;
|
||||
// Deprecated, same display same bpp?
|
||||
// https://stackoverflow.com/questions/8210824/how-to-avoid-cgdisplaymodecopypixelencoding-to-get-bpp
|
||||
// https://github.com/libsdl-org/SDL/pull/6628
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display);
|
||||
if (currentMode == NULL) {
|
||||
return false;
|
||||
}
|
||||
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
|
||||
if (allModes == NULL) {
|
||||
CGDisplayModeRelease(currentMode);
|
||||
return false;
|
||||
}
|
||||
uint32_t allModeCount = CFArrayGetCount(allModes);
|
||||
uint32_t realNum = 0;
|
||||
for (uint32_t i = 0; i < allModeCount && realNum < max; i++) {
|
||||
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
|
||||
if (CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode) &&
|
||||
bitDepth(currentMode) == bitDepth(mode)) {
|
||||
widths[realNum] = (uint32_t)CGDisplayModeGetWidth(mode);
|
||||
heights[realNum] = (uint32_t)CGDisplayModeGetHeight(mode);
|
||||
realNum++;
|
||||
}
|
||||
}
|
||||
*numModes = realNum;
|
||||
CGDisplayModeRelease(currentMode);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
static 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);
|
||||
for (int i = 0; i < numModes; i++) {
|
||||
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
|
||||
if (width == CGDisplayModeGetWidth(mode) &&
|
||||
height == CGDisplayModeGetHeight(mode) &&
|
||||
CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode) &&
|
||||
bitDepth(currentMode) == bitDepth(mode)) {
|
||||
ret = setDisplayToMode(display, mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CGDisplayModeRelease(currentMode);
|
||||
CFRelease(allModes);
|
||||
return ret;
|
||||
}
|
||||
@@ -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,17 @@ extern "C" {
|
||||
static kAXTrustedCheckOptionPrompt: CFStringRef;
|
||||
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
|
||||
fn InputMonitoringAuthStatus(_: BOOL) -> BOOL;
|
||||
fn MacCheckAdminAuthorization() -> 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 +182,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 +217,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();
|
||||
}
|
||||
}
|
||||
@@ -331,7 +349,7 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
|
||||
*/
|
||||
let mut colors: Vec<u8> = Vec::new();
|
||||
colors.reserve((size.height * size.width) as usize * 4);
|
||||
// TIFF is rgb colrspace, no need to convert
|
||||
// TIFF is rgb colorspace, no need to convert
|
||||
// let cs: id = msg_send![class!(NSColorSpace), sRGBColorSpace];
|
||||
for y in 0..(size.height as _) {
|
||||
for x in 0..(size.width as _) {
|
||||
@@ -440,7 +458,7 @@ pub fn start_os_service() {
|
||||
.status()
|
||||
.ok();
|
||||
println!("The others killed");
|
||||
// launchctl load/unload/start agent not work in daemon, show not priviledged.
|
||||
// launchctl load/unload/start agent not work in daemon, show not privileged.
|
||||
// sudo launchctl asuser 501 open -n also not allowed.
|
||||
std::process::Command::new("launchctl")
|
||||
.args(&[
|
||||
@@ -541,7 +559,6 @@ pub fn is_installed() -> bool {
|
||||
}
|
||||
|
||||
pub fn quit_gui() {
|
||||
use cocoa::appkit::NSApp;
|
||||
unsafe {
|
||||
let () = msg_send!(NSApp(), terminate: nil);
|
||||
};
|
||||
@@ -557,3 +574,102 @@ pub fn hide_dock() {
|
||||
NSApp().setActivationPolicy_(NSApplicationActivationPolicyAccessory);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
let my_uid = sys
|
||||
.process((std::process::id() as i32).into())
|
||||
.map(|x| x.user_id())
|
||||
.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 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 real_num = 0;
|
||||
if YES
|
||||
== MacGetModes(
|
||||
display,
|
||||
widths.as_mut_ptr(),
|
||||
heights.as_mut_ptr(),
|
||||
num,
|
||||
&mut real_num,
|
||||
)
|
||||
{
|
||||
if real_num <= num {
|
||||
for i in 0..real_num {
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
pub fn check_super_user_permission() -> ResultType<bool> {
|
||||
unsafe {
|
||||
Ok(MacCheckAdminAuthorization() == YES)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ pub mod windows;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod delegate;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
|
||||
@@ -74,5 +77,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ 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::{
|
||||
ffi::OsString,
|
||||
fs, io, mem,
|
||||
os::windows::process::CommandExt,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
@@ -24,7 +27,7 @@ use winapi::{
|
||||
minwinbase::STILL_ACTIVE,
|
||||
processthreadsapi::{
|
||||
GetCurrentProcess, GetCurrentProcessId, GetExitCodeProcess, OpenProcess,
|
||||
OpenProcessToken,
|
||||
OpenProcessToken, PROCESS_INFORMATION, STARTUPINFOW,
|
||||
},
|
||||
securitybaseapi::GetTokenInformation,
|
||||
shellapi::ShellExecuteW,
|
||||
@@ -49,6 +52,7 @@ use winreg::RegKey;
|
||||
|
||||
pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
unsafe {
|
||||
#[allow(invalid_value)]
|
||||
let mut out = mem::MaybeUninit::uninit().assume_init();
|
||||
if GetCursorPos(&mut out) == FALSE {
|
||||
return None;
|
||||
@@ -61,6 +65,7 @@ pub fn reset_input_cache() {}
|
||||
|
||||
pub fn get_cursor() -> ResultType<Option<u64>> {
|
||||
unsafe {
|
||||
#[allow(invalid_value)]
|
||||
let mut ci: CURSORINFO = mem::MaybeUninit::uninit().assume_init();
|
||||
ci.cbSize = std::mem::size_of::<CURSORINFO>() as _;
|
||||
if crate::portable_service::client::get_cursor_info(&mut ci) == FALSE {
|
||||
@@ -79,6 +84,7 @@ struct IconInfo(ICONINFO);
|
||||
impl IconInfo {
|
||||
fn new(icon: HICON) -> ResultType<Self> {
|
||||
unsafe {
|
||||
#[allow(invalid_value)]
|
||||
let mut ii = mem::MaybeUninit::uninit().assume_init();
|
||||
if GetIconInfo(icon, &mut ii) == FALSE {
|
||||
Err(io::Error::last_os_error().into())
|
||||
@@ -829,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() {
|
||||
@@ -839,6 +845,11 @@ pub fn check_update_broker_process() -> ResultType<()> {
|
||||
let cur_dir = exe_file.parent().unwrap();
|
||||
let cur_exe = cur_dir.join(process_exe);
|
||||
|
||||
if !std::path::Path::new(&cur_exe).exists() {
|
||||
std::fs::copy(origin_process_exe, cur_exe)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let ori_modified = fs::metadata(origin_process_exe)?.modified()?;
|
||||
if let Ok(metadata) = fs::metadata(&cur_exe) {
|
||||
if let Ok(cur_modified) = metadata.modified() {
|
||||
@@ -910,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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -929,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(),
|
||||
@@ -965,7 +976,7 @@ fn get_after_install(exe: &str) -> String {
|
||||
}
|
||||
|
||||
pub fn install_me(options: &str, path: String, silent: bool, debug: bool) -> ResultType<()> {
|
||||
let uninstall_str = get_uninstall();
|
||||
let uninstall_str = get_uninstall(false);
|
||||
let mut path = path.trim_end_matches('\\').to_owned();
|
||||
let (subkey, _path, start_menu, exe) = get_default_install_info();
|
||||
let mut exe = exe;
|
||||
@@ -1177,30 +1188,35 @@ pub fn run_after_install() -> ResultType<()> {
|
||||
}
|
||||
|
||||
pub fn run_before_uninstall() -> ResultType<()> {
|
||||
run_cmds(get_before_uninstall(), true, "before_install")
|
||||
run_cmds(get_before_uninstall(true), true, "before_install")
|
||||
}
|
||||
|
||||
fn get_before_uninstall() -> String {
|
||||
fn get_before_uninstall(kill_self: bool) -> String {
|
||||
let app_name = crate::get_app_name();
|
||||
let ext = app_name.to_lowercase();
|
||||
let filter = if kill_self {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!(" /FI \"PID ne {}\"", get_current_pid())
|
||||
};
|
||||
format!(
|
||||
"
|
||||
chcp 65001
|
||||
sc stop {app_name}
|
||||
sc delete {app_name}
|
||||
taskkill /F /IM {broker_exe}
|
||||
taskkill /F /IM {app_name}.exe /FI \"PID ne {cur_pid}\"
|
||||
taskkill /F /IM {app_name}.exe{filter}
|
||||
reg delete HKEY_CLASSES_ROOT\\.{ext} /f
|
||||
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(),
|
||||
filter = filter,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_uninstall() -> String {
|
||||
fn get_uninstall(kill_self: bool) -> String {
|
||||
let (subkey, path, start_menu, _) = get_install_info();
|
||||
format!(
|
||||
"
|
||||
@@ -1211,7 +1227,7 @@ fn get_uninstall() -> String {
|
||||
if exist \"%PUBLIC%\\Desktop\\{app_name}.lnk\" del /f /q \"%PUBLIC%\\Desktop\\{app_name}.lnk\"
|
||||
if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\"
|
||||
",
|
||||
before_uninstall=get_before_uninstall(),
|
||||
before_uninstall=get_before_uninstall(kill_self),
|
||||
subkey=subkey,
|
||||
app_name = crate::get_app_name(),
|
||||
path = path,
|
||||
@@ -1220,11 +1236,20 @@ fn get_uninstall() -> String {
|
||||
}
|
||||
|
||||
pub fn uninstall_me() -> ResultType<()> {
|
||||
run_cmds(get_uninstall(), true, "uninstall")
|
||||
run_cmds(get_uninstall(true), true, "uninstall")
|
||||
}
|
||||
|
||||
fn write_cmds(cmds: String, ext: &str, tip: &str) -> ResultType<std::path::PathBuf> {
|
||||
let mut tmp = std::env::temp_dir();
|
||||
// When dir contains these characters, the bat file will not execute in elevated mode.
|
||||
if vec!["&", "@", "^"]
|
||||
.drain(..)
|
||||
.any(|s| tmp.to_string_lossy().to_string().contains(s))
|
||||
{
|
||||
if let Ok(dir) = user_accessible_folder() {
|
||||
tmp = dir;
|
||||
}
|
||||
}
|
||||
tmp.push(format!("{}_{}.{}", crate::get_app_name(), tip, ext));
|
||||
let mut file = std::fs::File::create(&tmp)?;
|
||||
// in case cmds mixed with \r\n and \n, make sure all ending with \r\n
|
||||
@@ -1368,22 +1393,6 @@ pub fn get_license() -> Option<License> {
|
||||
pub fn bootstrap() {
|
||||
if let Some(lic) = get_license() {
|
||||
*config::PROD_RENDEZVOUS_SERVER.write().unwrap() = lic.host.clone();
|
||||
#[cfg(feature = "hbbs")]
|
||||
{
|
||||
if !is_win_server() {
|
||||
return;
|
||||
}
|
||||
crate::hbbs::bootstrap(&lic.key, &lic.host);
|
||||
std::thread::spawn(move || loop {
|
||||
let tmp = Config::get_option("stop-rendezvous-service");
|
||||
if tmp.is_empty() {
|
||||
crate::hbbs::start();
|
||||
} else {
|
||||
crate::hbbs::stop();
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1652,6 +1661,29 @@ pub fn is_elevated(process_id: Option<DWORD>) -> ResultType<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn filter_foreground_window(process_id: DWORD) -> ResultType<bool> {
|
||||
if let Ok(output) = std::process::Command::new("tasklist")
|
||||
.args(vec![
|
||||
"/SVC",
|
||||
"/NH",
|
||||
"/FI",
|
||||
&format!("PID eq {}", process_id),
|
||||
])
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output()
|
||||
{
|
||||
let s = String::from_utf8_lossy(&output.stdout)
|
||||
.to_string()
|
||||
.to_lowercase();
|
||||
Ok(["Taskmgr", "mmc", "regedit"]
|
||||
.iter()
|
||||
.any(|name| s.contains(&name.to_string().to_lowercase())))
|
||||
} else {
|
||||
bail!("run tasklist failed");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_foreground_window_elevated() -> ResultType<bool> {
|
||||
unsafe {
|
||||
let mut process_id: DWORD = 0;
|
||||
@@ -1659,7 +1691,12 @@ pub fn is_foreground_window_elevated() -> ResultType<bool> {
|
||||
if process_id == 0 {
|
||||
bail!("Failed to get processId, errno {}", GetLastError())
|
||||
}
|
||||
is_elevated(Some(process_id))
|
||||
let elevated = is_elevated(Some(process_id))?;
|
||||
if elevated {
|
||||
filter_foreground_window(process_id)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1714,3 +1751,154 @@ pub fn send_message_to_hnwd(
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn create_process_with_logon(user: &str, pwd: &str, exe: &str, arg: &str) -> ResultType<()> {
|
||||
unsafe {
|
||||
let wuser = wide_string(user);
|
||||
let wpc = wide_string("");
|
||||
let wpwd = wide_string(pwd);
|
||||
let cmd = if arg.is_empty() {
|
||||
format!("\"{}\"", exe)
|
||||
} else {
|
||||
format!("\"{}\" {}", exe, arg)
|
||||
};
|
||||
let mut wcmd = wide_string(&cmd);
|
||||
let mut si: STARTUPINFOW = mem::zeroed();
|
||||
si.wShowWindow = SW_HIDE as _;
|
||||
si.lpDesktop = NULL as _;
|
||||
si.cb = std::mem::size_of::<STARTUPINFOW>() as _;
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
let mut pi: PROCESS_INFORMATION = mem::zeroed();
|
||||
let wexe = wide_string(exe);
|
||||
if FALSE
|
||||
== CreateProcessWithLogonW(
|
||||
wuser.as_ptr(),
|
||||
wpc.as_ptr(),
|
||||
wpwd.as_ptr(),
|
||||
LOGON_WITH_PROFILE,
|
||||
wexe.as_ptr(),
|
||||
wcmd.as_mut_ptr(),
|
||||
CREATE_UNICODE_ENVIRONMENT,
|
||||
NULL,
|
||||
NULL as _,
|
||||
&mut si as *mut STARTUPINFOW,
|
||||
&mut pi as *mut PROCESS_INFORMATION,
|
||||
)
|
||||
{
|
||||
bail!("CreateProcessWithLogonW failed, errno={}", GetLastError());
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn set_path_permission(dir: &PathBuf, permission: &str) -> ResultType<()> {
|
||||
std::process::Command::new("icacls")
|
||||
.arg(dir.as_os_str())
|
||||
.arg("/grant")
|
||||
.arg(format!("Everyone:(OI)(CI){}", permission))
|
||||
.arg("/T")
|
||||
.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(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_accessible_folder() -> ResultType<PathBuf> {
|
||||
let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string());
|
||||
let dir1 = PathBuf::from(format!("{}\\ProgramData", disk));
|
||||
// NOTICE: "C:\Windows\Temp" requires permanent authorization.
|
||||
let dir2 = PathBuf::from(format!("{}\\Windows\\Temp", disk));
|
||||
let dir;
|
||||
if dir1.exists() {
|
||||
dir = dir1;
|
||||
} else if dir2.exists() {
|
||||
dir = dir2;
|
||||
} else {
|
||||
bail!("no vaild user accessible folder");
|
||||
}
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
@@ -469,10 +469,10 @@ impl RendezvousMediator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_relay_server(&self, provided_by_rendzvous_server: String) -> String {
|
||||
fn get_relay_server(&self, provided_by_rendezvous_server: String) -> String {
|
||||
let mut relay_server = Config::get_option("relay-server");
|
||||
if relay_server.is_empty() {
|
||||
relay_server = provided_by_rendzvous_server;
|
||||
relay_server = provided_by_rendezvous_server;
|
||||
}
|
||||
if relay_server.is_empty() {
|
||||
relay_server = crate::increase_port(&self.host, 1);
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
use crate::ipc::Data;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::SocketAddr,
|
||||
sync::{Arc, Mutex, RwLock, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
pub use connection::*;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::config::Config2;
|
||||
use hbb_common::tcp::new_listener;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::{anyhow, Context},
|
||||
@@ -19,12 +27,8 @@ use hbb_common::{
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use service::ServiceTmpl;
|
||||
use service::{GenericService, Service, Subscriber};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::SocketAddr,
|
||||
sync::{Arc, Mutex, RwLock, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::ipc::Data;
|
||||
|
||||
pub mod audio_service;
|
||||
cfg_if::cfg_if! {
|
||||
@@ -55,14 +59,19 @@ mod service;
|
||||
mod video_qos;
|
||||
pub mod video_service;
|
||||
|
||||
use hbb_common::tcp::new_listener;
|
||||
|
||||
pub type Childs = Arc<Mutex<Vec<std::process::Child>>>;
|
||||
type ConnMap = HashMap<i32, ConnInner>;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref CHILD_PROCESS: Childs = Default::default();
|
||||
pub static ref CONN_COUNT: Arc<Mutex<usize>> = Default::default();
|
||||
// A client server used to provide local services(audio, video, clipboard, etc.)
|
||||
// for all initiative connections.
|
||||
//
|
||||
// [Note]
|
||||
// Now we use this [`CLIENT_SERVER`] to do following operations:
|
||||
// - record local audio, and send to remote
|
||||
pub static ref CLIENT_SERVER: ServerPtr = new();
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
@@ -194,9 +203,14 @@ pub async fn create_tcp_connection(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]{
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use std::process::Command;
|
||||
Command::new("/usr/bin/caffeinate").arg("-u").arg("-t 5").spawn().ok();
|
||||
Command::new("/usr/bin/caffeinate")
|
||||
.arg("-u")
|
||||
.arg("-t 5")
|
||||
.spawn()
|
||||
.ok();
|
||||
log::info!("wake up macos");
|
||||
}
|
||||
Connection::start(addr, stream, id, Arc::downgrade(&server)).await;
|
||||
@@ -309,6 +323,13 @@ impl Server {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get a new unique id
|
||||
pub fn get_new_id(&mut self) -> i32 {
|
||||
let new_id = self.id_count;
|
||||
self.id_count += 1;
|
||||
new_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Server {
|
||||
@@ -385,6 +406,7 @@ pub async fn start_server(is_server: bool) {
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::bootstrap();
|
||||
input_service::fix_key_down_timeout_loop();
|
||||
crate::hbbs_http::sync::start();
|
||||
#[cfg(target_os = "linux")]
|
||||
if crate::platform::current_is_wayland() {
|
||||
allow_err!(input_service::setup_uinput(0, 1920, 0, 1080).await);
|
||||
@@ -398,7 +420,8 @@ pub async fn start_server(is_server: bool) {
|
||||
if conn.send(&Data::SyncConfig(None)).await.is_ok() {
|
||||
if let Ok(Some(data)) = conn.next_timeout(1000).await {
|
||||
match data {
|
||||
Data::SyncConfig(Some((config, config2))) => {
|
||||
Data::SyncConfig(Some(configs)) => {
|
||||
let (config, config2) = *configs;
|
||||
if Config::set(config) {
|
||||
log::info!("config synced");
|
||||
}
|
||||
@@ -419,6 +442,48 @@ pub async fn start_server(is_server: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_ipc_url_server() {
|
||||
log::debug!("Start an ipc server for listening to url schemes");
|
||||
match crate::ipc::new_listener("_url").await {
|
||||
Ok(mut incoming) => {
|
||||
while let Some(Ok(conn)) = incoming.next().await {
|
||||
let mut conn = crate::ipc::Connection::new(conn);
|
||||
match conn.next_timeout(1000).await {
|
||||
Ok(Some(data)) => match data {
|
||||
#[cfg(feature = "flutter")]
|
||||
Data::UrlLink(url) => {
|
||||
if let Some(stream) = crate::flutter::GLOBAL_EVENT_STREAM
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(crate::flutter::APP_TYPE_MAIN)
|
||||
{
|
||||
let mut m = HashMap::new();
|
||||
m.insert("name", "on_url_scheme_received");
|
||||
m.insert("url", url.as_str());
|
||||
stream.add(serde_json::to_string(&m).unwrap());
|
||||
} else {
|
||||
log::warn!("No main window app found!");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::warn!("An unexpected data was sent to the ipc url server.")
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("{}", err);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn sync_and_watch_config_dir() {
|
||||
if crate::platform::is_root() {
|
||||
@@ -443,7 +508,8 @@ async fn sync_and_watch_config_dir() {
|
||||
if conn.send(&Data::SyncConfig(None)).await.is_ok() {
|
||||
if let Ok(Some(data)) = conn.next_timeout(1000).await {
|
||||
match data {
|
||||
Data::SyncConfig(Some((config, config2))) => {
|
||||
Data::SyncConfig(Some(configs)) => {
|
||||
let (config, config2) = *configs;
|
||||
let _chk = crate::ipc::CheckIfRestart::new();
|
||||
if cfg0.0 != config {
|
||||
cfg0.0 = config.clone();
|
||||
@@ -468,7 +534,7 @@ async fn sync_and_watch_config_dir() {
|
||||
let cfg = (Config::get(), Config2::get());
|
||||
if cfg != cfg0 {
|
||||
log::info!("config updated, sync to root");
|
||||
match conn.send(&Data::SyncConfig(Some(cfg.clone()))).await {
|
||||
match conn.send(&Data::SyncConfig(Some(cfg.clone().into()))).await {
|
||||
Err(e) => {
|
||||
log::error!("sync config to root failed: {}", e);
|
||||
break;
|
||||
|
||||
@@ -3,7 +3,16 @@ use super::{input_service::*, *};
|
||||
use crate::clipboard_file::*;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::common::update_clipboard;
|
||||
use crate::video_service;
|
||||
#[cfg(windows)]
|
||||
use crate::portable_service::client as portable_client;
|
||||
use crate::{
|
||||
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,
|
||||
};
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
|
||||
use crate::{ipc, VERSION};
|
||||
@@ -26,12 +35,14 @@ use hbb_common::{
|
||||
};
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
use scrap::android::call_main_service_mouse_input;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, value::Value};
|
||||
use sha2::{Digest, Sha256};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{atomic::AtomicI64, mpsc as std_mpsc};
|
||||
use std::{
|
||||
num::NonZeroI64,
|
||||
sync::{atomic::AtomicI64, mpsc as std_mpsc},
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use system_shutdown;
|
||||
|
||||
@@ -40,6 +51,8 @@ pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>;
|
||||
lazy_static::lazy_static! {
|
||||
static ref LOGIN_FAILURES: Arc::<Mutex<HashMap<String, (i32, i32, i32)>>> = Default::default();
|
||||
static ref SESSIONS: Arc::<Mutex<HashMap<String, Session>>> = Default::default();
|
||||
static ref ALIVE_CONNS: Arc::<Mutex<Vec<i32>>> = Default::default();
|
||||
static ref SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid)>>> = Default::default();
|
||||
}
|
||||
pub static CLICK_TIME: AtomicI64 = AtomicI64::new(0);
|
||||
pub static MOUSE_MOVE_TIME: AtomicI64 = AtomicI64::new(0);
|
||||
@@ -74,7 +87,6 @@ pub struct Connection {
|
||||
read_jobs: Vec<fs::TransferJob>,
|
||||
timer: Interval,
|
||||
file_timer: Interval,
|
||||
http_timer: Interval,
|
||||
file_transfer: Option<(String, bool)>,
|
||||
port_forward_socket: Option<Framed<TcpStream, BytesCodec>>,
|
||||
port_forward_address: String,
|
||||
@@ -88,12 +100,19 @@ pub struct Connection {
|
||||
recording: bool,
|
||||
last_test_delay: i64,
|
||||
lock_after_session_end: bool,
|
||||
show_remote_cursor: bool, // by peer
|
||||
show_remote_cursor: bool,
|
||||
// by peer
|
||||
ip: String,
|
||||
disable_clipboard: bool, // by peer
|
||||
disable_audio: bool, // by peer
|
||||
enable_file_transfer: bool, // by peer
|
||||
tx_input: std_mpsc::Sender<MessageInput>, // handle input messages
|
||||
disable_clipboard: bool,
|
||||
// by peer
|
||||
disable_audio: bool,
|
||||
// by peer
|
||||
enable_file_transfer: bool,
|
||||
// by peer
|
||||
audio_sender: Option<MediaSender>,
|
||||
// audio by the remote peer/client
|
||||
tx_input: std_mpsc::Sender<MessageInput>,
|
||||
// handle input messages
|
||||
video_ack_required: bool,
|
||||
peer_info: (String, String),
|
||||
server_audit_conn: String,
|
||||
@@ -101,7 +120,19 @@ pub struct Connection {
|
||||
lr: LoginRequest,
|
||||
last_recv_time: Arc<Mutex<Instant>>,
|
||||
chat_unanswered: bool,
|
||||
close_manually: bool,
|
||||
#[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>,
|
||||
options_in_login: Option<OptionMessage>,
|
||||
}
|
||||
|
||||
impl ConnInner {
|
||||
pub fn new(id: i32, tx: Option<Sender>, tx_video: Option<Sender>) -> Self {
|
||||
Self { id, tx, tx_video }
|
||||
}
|
||||
}
|
||||
|
||||
impl Subscriber for ConnInner {
|
||||
@@ -147,14 +178,15 @@ impl Connection {
|
||||
challenge: Config::get_auto_password(6),
|
||||
..Default::default()
|
||||
};
|
||||
ALIVE_CONNS.lock().unwrap().push(id);
|
||||
let (tx_from_cm_holder, mut rx_from_cm) = mpsc::unbounded_channel::<ipc::Data>();
|
||||
// holding tx_from_cm_holde to avoid cpu burning of rx_from_cm.recv when all sender closed
|
||||
// holding tx_from_cm_holder to avoid cpu burning of rx_from_cm.recv when all sender closed
|
||||
let tx_from_cm = tx_from_cm_holder.clone();
|
||||
let (tx_to_cm, rx_to_cm) = mpsc::unbounded_channel::<ipc::Data>();
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<(Instant, Arc<Message>)>();
|
||||
let (tx_video, mut rx_video) = mpsc::unbounded_channel::<(Instant, Arc<Message>)>();
|
||||
let (tx_input, rx_input) = std_mpsc::channel();
|
||||
let (tx_stop, mut rx_stop) = mpsc::unbounded_channel::<String>();
|
||||
let mut hbbs_rx = crate::hbbs_http::sync::signal_receiver();
|
||||
|
||||
let tx_cloned = tx.clone();
|
||||
let mut conn = Self {
|
||||
@@ -169,7 +201,6 @@ impl Connection {
|
||||
read_jobs: Vec::new(),
|
||||
timer: time::interval(SEC30),
|
||||
file_timer: time::interval(SEC30),
|
||||
http_timer: time::interval(Duration::from_secs(3)),
|
||||
file_transfer: None,
|
||||
port_forward_socket: None,
|
||||
port_forward_address: "".to_owned(),
|
||||
@@ -196,7 +227,14 @@ impl Connection {
|
||||
lr: Default::default(),
|
||||
last_recv_time: Arc::new(Mutex::new(Instant::now())),
|
||||
chat_unanswered: false,
|
||||
close_manually: false,
|
||||
#[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,
|
||||
options_in_login: None,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
tokio::spawn(async move {
|
||||
@@ -243,12 +281,6 @@ impl Connection {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
std::thread::spawn(move || Self::handle_input(rx_input, tx_cloned));
|
||||
let mut second_timer = time::interval(Duration::from_secs(1));
|
||||
#[cfg(windows)]
|
||||
let mut last_uac = false;
|
||||
#[cfg(windows)]
|
||||
let mut last_foreground_window_elevated = false;
|
||||
#[cfg(windows)]
|
||||
let is_installed = crate::platform::is_installed();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -263,7 +295,9 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
ipc::Data::Close => {
|
||||
conn.on_close_manually("connection manager", "peer").await;
|
||||
conn.chat_unanswered = false; // seen
|
||||
conn.send_close_reason_no_retry("").await;
|
||||
conn.on_close("connection manager", true).await;
|
||||
break;
|
||||
}
|
||||
ipc::Data::ChatMessage{text} => {
|
||||
@@ -319,7 +353,7 @@ impl Connection {
|
||||
allow_err!(conn.stream.send_raw(bytes).await);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
ipc::Data::ClipbaordFile(_clip) => {
|
||||
ipc::Data::ClipboardFile(_clip) => {
|
||||
if conn.file_transfer_enabled() {
|
||||
allow_err!(conn.stream.send(&clip_2_msg(_clip)).await);
|
||||
}
|
||||
@@ -354,10 +388,27 @@ impl Connection {
|
||||
}
|
||||
#[cfg(windows)]
|
||||
ipc::Data::DataPortableService(ipc::DataPortableService::RequestStart) => {
|
||||
if let Err(e) = crate::portable_service::client::start_portable_service() {
|
||||
if let Err(e) = portable_client::start_portable_service(portable_client::StartPara::Direct) {
|
||||
log::error!("Failed to start portable service from cm:{:?}", e);
|
||||
}
|
||||
}
|
||||
ipc::Data::SwitchSidesBack => {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_switch_back(SwitchBack::default());
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
conn.send(msg).await;
|
||||
}
|
||||
ipc::Data::VoiceCallResponse(accepted) => {
|
||||
conn.handle_voice_call(accepted).await;
|
||||
}
|
||||
ipc::Data::CloseVoiceCall(_reason) => {
|
||||
log::debug!("Close the voice call from the ipc.");
|
||||
conn.close_voice_call().await;
|
||||
// Notify the peer that we closed the voice call.
|
||||
let msg = new_voice_call_request(false);
|
||||
conn.send(msg).await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
@@ -393,12 +444,12 @@ impl Connection {
|
||||
conn.file_timer = time::interval_at(Instant::now() + SEC30, SEC30);
|
||||
}
|
||||
}
|
||||
_ = conn.http_timer.tick() => {
|
||||
Connection::post_heartbeat(conn.server_audit_conn.clone(), conn.inner.id, tx_stop.clone());
|
||||
},
|
||||
Some(reason) = rx_stop.recv() => {
|
||||
conn.on_close_manually(&reason, &reason).await;
|
||||
|
||||
Ok(conns) = hbbs_rx.recv() => {
|
||||
if conns.contains(&id) {
|
||||
conn.send_close_reason_no_retry("Closed manually by web console").await;
|
||||
conn.on_close("web console", true).await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some((instant, value)) = rx_video.recv() => {
|
||||
if !conn.video_ack_required {
|
||||
@@ -426,7 +477,8 @@ impl Connection {
|
||||
Some(message::Union::Misc(m)) => {
|
||||
match &m.union {
|
||||
Some(misc::Union::StopService(_)) => {
|
||||
conn.on_close_manually("stop service", "peer").await;
|
||||
conn.send_close_reason_no_retry("").await;
|
||||
conn.on_close("stop service", true).await;
|
||||
break;
|
||||
}
|
||||
_ => {},
|
||||
@@ -441,36 +493,7 @@ impl Connection {
|
||||
},
|
||||
_ = second_timer.tick() => {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if !is_installed {
|
||||
let portable_service_running = crate::portable_service::client::PORTABLE_SERVICE_RUNNING.lock().unwrap().clone();
|
||||
let uac = crate::video_service::IS_UAC_RUNNING.lock().unwrap().clone();
|
||||
if last_uac != uac {
|
||||
last_uac = uac;
|
||||
if !uac || !portable_service_running{
|
||||
let mut misc = Misc::new();
|
||||
misc.set_uac(uac);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
conn.inner.send(msg.into());
|
||||
}
|
||||
}
|
||||
let foreground_window_elevated = crate::video_service::IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap().clone();
|
||||
if last_foreground_window_elevated != foreground_window_elevated {
|
||||
last_foreground_window_elevated = foreground_window_elevated;
|
||||
if !foreground_window_elevated || !portable_service_running {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_foreground_window_elevated(foreground_window_elevated);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
conn.inner.send(msg.into());
|
||||
}
|
||||
}
|
||||
let show_elevation = !portable_service_running;
|
||||
conn.send_to_cm(ipc::Data::DataPortableService(ipc::DataPortableService::CmShowElevation(show_elevation)));
|
||||
|
||||
}
|
||||
}
|
||||
conn.portable_check();
|
||||
}
|
||||
_ = test_delay_timer.tick() => {
|
||||
if last_recv_time.elapsed() >= SEC30 {
|
||||
@@ -514,6 +537,12 @@ 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);
|
||||
}
|
||||
log::info!("#{} connection loop exited", id);
|
||||
}
|
||||
|
||||
@@ -584,16 +613,16 @@ impl Connection {
|
||||
rx_from_cm: &mut mpsc::UnboundedReceiver<Data>,
|
||||
) -> ResultType<()> {
|
||||
let mut last_recv_time = Instant::now();
|
||||
let (tx_stop, mut rx_stop) = mpsc::unbounded_channel::<String>();
|
||||
if let Some(mut forward) = self.port_forward_socket.take() {
|
||||
log::info!("Running port forwarding loop");
|
||||
self.stream.set_raw();
|
||||
let mut hbbs_rx = crate::hbbs_http::sync::signal_receiver();
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(data) = rx_from_cm.recv() => {
|
||||
match data {
|
||||
ipc::Data::Close => {
|
||||
bail!("Close requested from selfection manager");
|
||||
bail!("Close requested from selection manager");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -618,10 +647,12 @@ impl Connection {
|
||||
if last_recv_time.elapsed() >= H1 {
|
||||
bail!("Timeout");
|
||||
}
|
||||
Connection::post_heartbeat(self.server_audit_conn.clone(), self.inner.id, tx_stop.clone());
|
||||
}
|
||||
Some(reason) = rx_stop.recv() => {
|
||||
bail!(reason);
|
||||
Ok(conns) = hbbs_rx.recv() => {
|
||||
if conns.contains(&self.inner.id) {
|
||||
// todo: check reconnect
|
||||
bail!("Closed manually by the web console");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,7 +694,7 @@ impl Connection {
|
||||
self.send_login_error("Your ip is blocked by the peer")
|
||||
.await;
|
||||
Self::post_alarm_audit(
|
||||
AlarmAuditType::IpWhiltelist, //"ip whiltelist",
|
||||
AlarmAuditType::IpWhitelist, //"ip whitelist",
|
||||
true,
|
||||
json!({
|
||||
"ip":addr.ip(),
|
||||
@@ -711,30 +742,6 @@ impl Connection {
|
||||
});
|
||||
}
|
||||
|
||||
fn post_heartbeat(
|
||||
server_audit_conn: String,
|
||||
conn_id: i32,
|
||||
tx_stop: mpsc::UnboundedSender<String>,
|
||||
) {
|
||||
if server_audit_conn.is_empty() {
|
||||
return;
|
||||
}
|
||||
let url = server_audit_conn.clone();
|
||||
let mut v = Value::default();
|
||||
v["id"] = json!(Config::get_id());
|
||||
v["uuid"] = json!(base64::encode(hbb_common::get_uuid()));
|
||||
v["conn_id"] = json!(conn_id);
|
||||
tokio::spawn(async move {
|
||||
if let Ok(rsp) = Self::post_audit_async(url, v).await {
|
||||
if let Ok(rsp) = serde_json::from_str::<ConnAuditResponse>(&rsp) {
|
||||
if rsp.action == "disconnect" {
|
||||
tx_stop.send("web console".to_string()).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn post_file_audit(
|
||||
&self,
|
||||
r#type: FileAuditType,
|
||||
@@ -880,6 +887,16 @@ impl Connection {
|
||||
..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() {
|
||||
@@ -895,16 +912,20 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_login_response(res);
|
||||
self.send(msg_out).await;
|
||||
if let Some(o) = self.options_in_login.take() {
|
||||
self.update_options(&o).await;
|
||||
}
|
||||
if let Some((dir, show_hidden)) = self.file_transfer.clone() {
|
||||
let dir = if !dir.is_empty() && std::path::Path::new(&dir).is_dir() {
|
||||
&dir
|
||||
@@ -962,6 +983,7 @@ impl Connection {
|
||||
file_transfer_enabled: self.file_transfer_enabled(),
|
||||
restart: self.restart,
|
||||
recording: self.recording,
|
||||
from_switch: self.from_switch,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1041,18 +1063,21 @@ impl Connection {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_of_recent_session(&mut self) -> bool {
|
||||
fn is_recent_session(&mut self) -> bool {
|
||||
let session = SESSIONS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&self.lr.my_id)
|
||||
.map(|s| s.to_owned());
|
||||
SESSIONS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.retain(|_, s| s.last_recv_time.lock().unwrap().elapsed() < SESSION_TIMEOUT);
|
||||
if let Some(session) = session {
|
||||
if session.name == self.lr.my_name
|
||||
&& session.session_id == self.lr.session_id
|
||||
&& !self.lr.password.is_empty()
|
||||
&& self.validate_one_password(session.random_password.clone())
|
||||
&& session.last_recv_time.lock().unwrap().elapsed() < SESSION_TIMEOUT
|
||||
{
|
||||
SESSIONS.lock().unwrap().insert(
|
||||
self.lr.my_id.clone(),
|
||||
@@ -1083,29 +1108,33 @@ impl Connection {
|
||||
return Config::get_option(enable_prefix_option).is_empty();
|
||||
}
|
||||
|
||||
async fn on_message(&mut self, msg: Message) -> bool {
|
||||
if let Some(message::Union::LoginRequest(lr)) = msg.union {
|
||||
self.lr = lr.clone();
|
||||
if let Some(o) = lr.option.as_ref() {
|
||||
self.update_option(o).await;
|
||||
if let Some(q) = o.video_codec_state.clone().take() {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::State(q),
|
||||
);
|
||||
} else {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||
);
|
||||
}
|
||||
async fn handle_login_request_without_validation(&mut self, lr: &LoginRequest) {
|
||||
self.lr = lr.clone();
|
||||
if let Some(o) = lr.option.as_ref() {
|
||||
self.options_in_login = Some(o.clone());
|
||||
if let Some(q) = o.video_codec_state.clone().take() {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::State(q),
|
||||
);
|
||||
} else {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||
);
|
||||
}
|
||||
self.video_ack_required = lr.video_ack_required;
|
||||
} else {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||
);
|
||||
}
|
||||
self.video_ack_required = lr.video_ack_required;
|
||||
}
|
||||
|
||||
async fn on_message(&mut self, msg: Message) -> bool {
|
||||
if let Some(message::Union::LoginRequest(lr)) = msg.union {
|
||||
self.handle_login_request_without_validation(&lr).await;
|
||||
if self.authorized {
|
||||
return true;
|
||||
}
|
||||
@@ -1178,7 +1207,7 @@ impl Connection {
|
||||
{
|
||||
self.send_login_error("Connection not allowed").await;
|
||||
return false;
|
||||
} else if self.is_of_recent_session() {
|
||||
} else if self.is_recent_session() {
|
||||
self.try_start_cm(lr.my_id, lr.my_name, true);
|
||||
self.send_logon_response().await;
|
||||
if self.port_forward_socket.is_some() {
|
||||
@@ -1252,6 +1281,25 @@ impl Connection {
|
||||
.unwrap()
|
||||
.update_network_delay(new_delay);
|
||||
}
|
||||
} else if let Some(message::Union::SwitchSidesResponse(_s)) = msg.union {
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(lr) = _s.lr.clone().take() {
|
||||
self.handle_login_request_without_validation(&lr).await;
|
||||
SWITCH_SIDES_UUID
|
||||
.lock()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.0.elapsed() < Duration::from_secs(10));
|
||||
let uuid_old = SWITCH_SIDES_UUID.lock().unwrap().remove(&lr.my_id);
|
||||
if let Ok(uuid) = uuid::Uuid::from_slice(_s.uuid.to_vec().as_ref()) {
|
||||
if let Some((_instant, uuid_old)) = uuid_old {
|
||||
if uuid == uuid_old {
|
||||
self.from_switch = true;
|
||||
self.try_start_cm(lr.my_id.clone(), lr.my_name.clone(), true);
|
||||
self.send_logon_response().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if self.authorized {
|
||||
match msg.union {
|
||||
Some(message::Union::MouseEvent(me)) => {
|
||||
@@ -1309,7 +1357,7 @@ impl Connection {
|
||||
if self.file_transfer_enabled() {
|
||||
#[cfg(windows)]
|
||||
if let Some(clip) = msg_2_clip(_clip) {
|
||||
self.send_to_cm(ipc::Data::ClipbaordFile(clip))
|
||||
self.send_to_cm(ipc::Data::ClipboardFile(clip))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1467,7 +1515,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 {
|
||||
@@ -1496,15 +1544,164 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(misc::Union::ElevationRequest(r)) => match r.union {
|
||||
Some(elevation_request::Union::Direct(_)) => {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut err = "No need to elevate".to_string();
|
||||
if !crate::platform::is_installed() && !portable_client::running() {
|
||||
err = portable_client::start_portable_service(
|
||||
portable_client::StartPara::Direct,
|
||||
)
|
||||
.err()
|
||||
.map_or("".to_string(), |e| e.to_string());
|
||||
}
|
||||
let mut misc = Misc::new();
|
||||
misc.set_elevation_response(err);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.send(msg).await;
|
||||
}
|
||||
}
|
||||
Some(elevation_request::Union::Logon(_r)) => {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut err = "No need to elevate".to_string();
|
||||
if !crate::platform::is_installed() && !portable_client::running() {
|
||||
err = portable_client::start_portable_service(
|
||||
portable_client::StartPara::Logon(_r.username, _r.password),
|
||||
)
|
||||
.err()
|
||||
.map_or("".to_string(), |e| e.to_string());
|
||||
}
|
||||
let mut misc = Misc::new();
|
||||
misc.set_elevation_response(err);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.send(msg).await;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(misc::Union::AudioFormat(format)) => {
|
||||
if !self.disable_audio {
|
||||
// Drop the audio sender previously.
|
||||
drop(std::mem::replace(&mut self.audio_sender, None));
|
||||
// Start a audio thread to play the audio sent by peer.
|
||||
let latency_controller = LatencyController::new();
|
||||
// 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)));
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "flutter")]
|
||||
Some(misc::Union::SwitchSidesRequest(s)) => {
|
||||
if let Ok(uuid) = uuid::Uuid::from_slice(&s.uuid.to_vec()[..]) {
|
||||
crate::run_me(vec![
|
||||
"--connect",
|
||||
&self.lr.my_id,
|
||||
"--switch_uuid",
|
||||
uuid.to_string().as_ref(),
|
||||
])
|
||||
.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)) => {
|
||||
if !self.disable_audio {
|
||||
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."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::VoiceCallRequest(request)) => {
|
||||
if request.is_connect {
|
||||
self.voice_call_request_timestamp = Some(
|
||||
NonZeroI64::new(request.req_timestamp)
|
||||
.unwrap_or(NonZeroI64::new(get_time()).unwrap()),
|
||||
);
|
||||
// Notify the connection manager.
|
||||
self.send_to_cm(Data::VoiceCallIncoming);
|
||||
} else {
|
||||
self.close_voice_call().await;
|
||||
}
|
||||
}
|
||||
Some(message::Union::VoiceCallResponse(_response)) => {
|
||||
// TODO: Maybe we can do a voice call from cm directly.
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
async fn update_option(&mut self, o: &OptionMessage) {
|
||||
pub async fn handle_voice_call(&mut self, accepted: bool) {
|
||||
if let Some(ts) = self.voice_call_request_timestamp.take() {
|
||||
let msg = new_voice_call_response(ts.get(), accepted);
|
||||
if accepted {
|
||||
// Backup the default input device.
|
||||
let audio_input_device = Config::get_option("audio-input");
|
||||
log::debug!("Backup the sound input device {}", audio_input_device);
|
||||
self.audio_input_device_before_voice_call = Some(audio_input_device);
|
||||
// Switch to default input device
|
||||
let default_sound_device = get_default_sound_input();
|
||||
if let Some(device) = default_sound_device {
|
||||
set_sound_input(device);
|
||||
}
|
||||
self.send_to_cm(Data::StartVoiceCall);
|
||||
} else {
|
||||
self.send_to_cm(Data::CloseVoiceCall("".to_owned()));
|
||||
}
|
||||
self.send(msg).await;
|
||||
} else {
|
||||
log::warn!("Possible a voice call attack.");
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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_options(&mut self, o: &OptionMessage) {
|
||||
log::info!("Option update: {:?}", o);
|
||||
if let Ok(q) = o.image_quality.enum_value() {
|
||||
let image_quality;
|
||||
@@ -1530,7 +1727,12 @@ 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),
|
||||
);
|
||||
}
|
||||
if let Ok(q) = o.lock_after_session_end.enum_value() {
|
||||
if q != BoolOption::NotSet {
|
||||
self.lock_after_session_end = q == BoolOption::Yes;
|
||||
@@ -1657,25 +1859,16 @@ 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 on_close(&mut self, reason: &str, lock: bool) {
|
||||
if let Some(s) = self.server.upgrade() {
|
||||
s.write().unwrap().remove_connection(&self.inner);
|
||||
}
|
||||
log::info!("#{} Connection closed: {}", self.inner.id(), reason);
|
||||
if lock && self.lock_after_session_end && self.keyboard {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
lock_screen().await;
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let data = if self.chat_unanswered && !self.close_manually {
|
||||
let data = if self.chat_unanswered {
|
||||
ipc::Data::Disconnected
|
||||
} else {
|
||||
ipc::Data::Close
|
||||
@@ -1686,15 +1879,17 @@ impl Connection {
|
||||
self.port_forward_socket.take();
|
||||
}
|
||||
|
||||
async fn on_close_manually(&mut self, close_from: &str, close_by: &str) {
|
||||
self.close_manually = true;
|
||||
// The `reason` should be consistent with `check_if_retry` if not empty
|
||||
async fn send_close_reason_no_retry(&mut self, reason: &str) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_close_reason(format!("Closed manually by the {}", close_by));
|
||||
if reason.is_empty() {
|
||||
misc.set_close_reason("Closed manually by the peer".to_string());
|
||||
} else {
|
||||
misc.set_close_reason(reason.to_string());
|
||||
}
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
self.send(msg_out).await;
|
||||
self.on_close(&format!("Close requested from {}", close_from), false)
|
||||
.await;
|
||||
SESSIONS.lock().unwrap().remove(&self.lr.my_id);
|
||||
}
|
||||
|
||||
@@ -1710,6 +1905,82 @@ impl Connection {
|
||||
async fn send(&mut self, msg: Message) {
|
||||
allow_err!(self.stream.send(&msg).await);
|
||||
}
|
||||
|
||||
pub fn alive_conns() -> Vec<i32> {
|
||||
ALIVE_CONNS.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn portable_check(&mut self) {
|
||||
if self.portable.is_installed
|
||||
|| self.file_transfer.is_some()
|
||||
|| self.port_forward_socket.is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let running = portable_client::running();
|
||||
let show_elevation = !running;
|
||||
self.send_to_cm(ipc::Data::DataPortableService(
|
||||
ipc::DataPortableService::CmShowElevation(show_elevation),
|
||||
));
|
||||
if self.authorized {
|
||||
let p = &mut self.portable;
|
||||
if running != p.last_running {
|
||||
p.last_running = running;
|
||||
let mut misc = Misc::new();
|
||||
misc.set_portable_service_running(running);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.inner.send(msg.into());
|
||||
}
|
||||
let uac = crate::video_service::IS_UAC_RUNNING.lock().unwrap().clone();
|
||||
if p.last_uac != uac {
|
||||
p.last_uac = uac;
|
||||
if !uac || !running {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_uac(uac);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.inner.send(msg.into());
|
||||
}
|
||||
}
|
||||
let foreground_window_elevated = crate::video_service::IS_FOREGROUND_WINDOW_ELEVATED
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone();
|
||||
if p.last_foreground_window_elevated != foreground_window_elevated {
|
||||
p.last_foreground_window_elevated = foreground_window_elevated;
|
||||
if !foreground_window_elevated || !running {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_foreground_window_elevated(foreground_window_elevated);
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.inner.send(msg.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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) {
|
||||
SWITCH_SIDES_UUID
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(id, (tokio::time::Instant::now(), uuid));
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -1833,7 +2104,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 {
|
||||
@@ -1857,8 +2128,8 @@ mod privacy_mode {
|
||||
pub(super) fn turn_on_privacy(_conn_id: i32) -> ResultType<bool> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let plugin_exitst = crate::ui::win_privacy::turn_on_privacy(_conn_id)?;
|
||||
Ok(plugin_exitst)
|
||||
let plugin_exist = crate::win_privacy::turn_on_privacy(_conn_id)?;
|
||||
Ok(plugin_exist)
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
@@ -1867,15 +2138,8 @@ mod privacy_mode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ConnAuditResponse {
|
||||
#[allow(dead_code)]
|
||||
ret: bool,
|
||||
action: String,
|
||||
}
|
||||
|
||||
pub enum AlarmAuditType {
|
||||
IpWhiltelist = 0,
|
||||
IpWhitelist = 0,
|
||||
ManyWrongPassword = 1,
|
||||
FrequentAttempt = 2,
|
||||
}
|
||||
@@ -1884,3 +2148,23 @@ pub enum FileAuditType {
|
||||
RemoteSend = 0,
|
||||
RemoteReceive = 1,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub struct PortableState {
|
||||
pub last_uac: bool,
|
||||
pub last_foreground_window_elevated: bool,
|
||||
pub last_running: bool,
|
||||
pub is_installed: bool,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl Default for PortableState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_installed: crate::platform::is_installed(),
|
||||
last_uac: Default::default(),
|
||||
last_foreground_window_elevated: Default::default(),
|
||||
last_running: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
/// [Flutter]: handle uni links for linux
|
||||
use dbus::blocking::Connection;
|
||||
use dbus_crossroads::{Crossroads, IfaceBuilder};
|
||||
use hbb_common::{log};
|
||||
use std::{error::Error, fmt, time::Duration};
|
||||
use hbb_common::log;
|
||||
#[cfg(feature = "flutter")]
|
||||
use std::collections::HashMap;
|
||||
use std::{error::Error, fmt, time::Duration};
|
||||
|
||||
const DBUS_NAME: &str = "org.rustdesk.rustdesk";
|
||||
const DBUS_PREFIX: &str = "/dbus";
|
||||
@@ -30,15 +30,16 @@ impl fmt::Display for DbusError {
|
||||
impl Error for DbusError {}
|
||||
|
||||
/// invoke new connection from dbus
|
||||
///
|
||||
///
|
||||
/// [Tips]:
|
||||
/// How to test by CLI:
|
||||
/// - use dbus-send command:
|
||||
/// `dbus-send --session --print-reply --dest=org.rustdesk.rustdesk /dbus org.rustdesk.rustdesk.NewConnection string:'PEER_ID'`
|
||||
pub fn invoke_new_connection(peer_id: String) -> Result<(), Box<dyn Error>> {
|
||||
pub fn invoke_new_connection(uni_links: String) -> Result<(), Box<dyn Error>> {
|
||||
let conn = Connection::new_session()?;
|
||||
let proxy = conn.with_proxy(DBUS_NAME, DBUS_PREFIX, DBUS_TIMEOUT);
|
||||
let (ret,): (String,) = proxy.method_call(DBUS_NAME, DBUS_METHOD_NEW_CONNECTION, (peer_id,))?;
|
||||
let (ret,): (String,) =
|
||||
proxy.method_call(DBUS_NAME, DBUS_METHOD_NEW_CONNECTION, (uni_links,))?;
|
||||
if ret != DBUS_METHOD_RETURN_SUCCESS {
|
||||
log::error!("error on call new connection to dbus server");
|
||||
return Err(Box::new(DbusError("not success".to_string())));
|
||||
@@ -67,7 +68,7 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) {
|
||||
DBUS_METHOD_NEW_CONNECTION,
|
||||
(DBUS_METHOD_NEW_CONNECTION_ID,),
|
||||
(DBUS_METHOD_RETURN,),
|
||||
move |_, _, (_peer_id,): (String,)| {
|
||||
move |_, _, (_uni_links,): (String,)| {
|
||||
#[cfg(feature = "flutter")]
|
||||
{
|
||||
use crate::flutter::{self, APP_TYPE_MAIN};
|
||||
@@ -79,7 +80,7 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) {
|
||||
{
|
||||
let data = HashMap::from([
|
||||
("name", "new_connection"),
|
||||
("peer_id", _peer_id.as_str())
|
||||
("uni_links", _uni_links.as_str()),
|
||||
]);
|
||||
if !stream.add(serde_json::ser::to_string(&data).unwrap_or("".to_string())) {
|
||||
log::error!("failed to add dbus message to flutter global dbus stream.");
|
||||
|
||||
@@ -71,7 +71,6 @@ struct Input {
|
||||
y: i32,
|
||||
}
|
||||
|
||||
const KEY_RDEV_START: u64 = 999;
|
||||
const KEY_CHAR_START: u64 = 9999;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@@ -202,11 +201,17 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
enum KeysDown {
|
||||
RdevKey(RawKey),
|
||||
EnigoKey(u64),
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref ENIGO: Arc<Mutex<Enigo>> = {
|
||||
Arc::new(Mutex::new(Enigo::new()))
|
||||
};
|
||||
static ref KEYS_DOWN: Arc<Mutex<HashMap<u64, Instant>>> = Default::default();
|
||||
static ref KEYS_DOWN: Arc<Mutex<HashMap<KeysDown, Instant>>> = Default::default();
|
||||
static ref LATEST_PEER_INPUT_CURSOR: Arc<Mutex<Input>> = Default::default();
|
||||
static ref LATEST_SYS_CURSOR_POS: Arc<Mutex<(Instant, (i32, i32))>> = Arc::new(Mutex::new((Instant::now().sub(MOUSE_MOVE_PROTECTION_TIMEOUT), (0, 0))));
|
||||
}
|
||||
@@ -375,12 +380,7 @@ fn record_key_is_control_key(record_key: u64) -> bool {
|
||||
|
||||
#[inline]
|
||||
fn record_key_is_chr(record_key: u64) -> bool {
|
||||
KEY_RDEV_START <= record_key && record_key < KEY_CHAR_START
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn record_key_is_rdev_layout(record_key: u64) -> bool {
|
||||
KEY_CHAR_START <= record_key
|
||||
record_key < KEY_CHAR_START
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -396,15 +396,16 @@ fn record_key_to_key(record_key: u64) -> Option<Key> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn release_record_key(record_key: u64) {
|
||||
let func = move || {
|
||||
if record_key_is_rdev_layout(record_key) {
|
||||
simulate_(&EventType::KeyRelease(RdevKey::Unknown(
|
||||
(record_key - KEY_RDEV_START) as _,
|
||||
)));
|
||||
} else if let Some(key) = record_key_to_key(record_key) {
|
||||
ENIGO.lock().unwrap().key_up(key);
|
||||
log::debug!("Fixed {:?} timeout", key);
|
||||
fn release_record_key(record_key: KeysDown) {
|
||||
let func = move || match record_key {
|
||||
KeysDown::RdevKey(raw_key) => {
|
||||
simulate_(&EventType::KeyRelease(RdevKey::RawKey(raw_key)));
|
||||
}
|
||||
KeysDown::EnigoKey(key) => {
|
||||
if let Some(key) = record_key_to_key(key) {
|
||||
ENIGO.lock().unwrap().key_up(key);
|
||||
log::debug!("Fixed {:?} timeout", key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -487,7 +488,7 @@ fn active_mouse_(conn: i32) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
let in_actived_dist = |a: i32, b: i32| -> bool { (a - b).abs() < MOUSE_ACTIVE_DISTANCE };
|
||||
let in_active_dist = |a: i32, b: i32| -> bool { (a - b).abs() < MOUSE_ACTIVE_DISTANCE };
|
||||
|
||||
// Check if input is in valid range
|
||||
match crate::get_cursor_pos() {
|
||||
@@ -496,7 +497,7 @@ fn active_mouse_(conn: i32) -> bool {
|
||||
let lock = LATEST_PEER_INPUT_CURSOR.lock().unwrap();
|
||||
(lock.x, lock.y)
|
||||
};
|
||||
let mut can_active = in_actived_dist(last_in_x, x) && in_actived_dist(last_in_y, y);
|
||||
let mut can_active = in_active_dist(last_in_x, x) && in_active_dist(last_in_y, y);
|
||||
// The cursor may not have been moved to last input position if system is busy now.
|
||||
// While this is not a common case, we check it again after some time later.
|
||||
if !can_active {
|
||||
@@ -505,7 +506,7 @@ fn active_mouse_(conn: i32) -> bool {
|
||||
std::thread::sleep(std::time::Duration::from_micros(10));
|
||||
// Sleep here can also somehow suppress delay accumulation.
|
||||
if let Some((x2, y2)) = crate::get_cursor_pos() {
|
||||
can_active = in_actived_dist(last_in_x, x2) && in_actived_dist(last_in_y, y2);
|
||||
can_active = in_active_dist(last_in_x, x2) && in_active_dist(last_in_y, y2);
|
||||
}
|
||||
}
|
||||
if !can_active {
|
||||
@@ -718,7 +719,7 @@ fn reset_input() {
|
||||
let _lock = VIRTUAL_INPUT_MTX.lock();
|
||||
VIRTUAL_INPUT = VirtualInput::new(
|
||||
CGEventSourceStateID::Private,
|
||||
CGEventTapLocation::AnnotatedSession,
|
||||
CGEventTapLocation::Session,
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
@@ -733,7 +734,7 @@ pub fn reset_input_ondisconn() {
|
||||
}
|
||||
}
|
||||
|
||||
fn sim_rdev_rawkey(code: u32, keydown: bool) {
|
||||
fn sim_rdev_rawkey_position(code: u32, keydown: bool) {
|
||||
#[cfg(target_os = "windows")]
|
||||
let rawkey = RawKey::ScanCode(code);
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -744,6 +745,21 @@ fn sim_rdev_rawkey(code: u32, keydown: bool) {
|
||||
#[cfg(target_os = "macos")]
|
||||
let rawkey = RawKey::MacVirtualKeycode(code);
|
||||
|
||||
// map mode(1): Send keycode according to the peer platform.
|
||||
record_pressed_key(KeysDown::RdevKey(rawkey), keydown);
|
||||
|
||||
let event_type = if keydown {
|
||||
EventType::KeyPress(RdevKey::RawKey(rawkey))
|
||||
} else {
|
||||
EventType::KeyRelease(RdevKey::RawKey(rawkey))
|
||||
};
|
||||
simulate_(&event_type);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn sim_rdev_rawkey_virtual(code: u32, keydown: bool) {
|
||||
let rawkey = RawKey::WinVirtualKeycode(code);
|
||||
record_pressed_key(KeysDown::RdevKey(rawkey), keydown);
|
||||
let event_type = if keydown {
|
||||
EventType::KeyPress(RdevKey::RawKey(rawkey))
|
||||
} else {
|
||||
@@ -874,15 +890,12 @@ fn sync_numlock_capslock_status(key_event: &KeyEvent) {
|
||||
}
|
||||
|
||||
fn map_keyboard_mode(evt: &KeyEvent) {
|
||||
// map mode(1): Send keycode according to the peer platform.
|
||||
record_pressed_key(evt.chr() as u64 + KEY_CHAR_START, evt.down);
|
||||
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::try_change_desktop();
|
||||
|
||||
// Wayland
|
||||
#[cfg(target_os = "linux")]
|
||||
if !*IS_X11.lock().unwrap() {
|
||||
if !*IS_X11 {
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
let code = evt.chr() as u16;
|
||||
|
||||
@@ -894,7 +907,7 @@ fn map_keyboard_mode(evt: &KeyEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
sim_rdev_rawkey(evt.chr(), evt.down);
|
||||
sim_rdev_rawkey_position(evt.chr(), evt.down);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -924,10 +937,11 @@ fn release_unpressed_modifiers(en: &mut Enigo, key_event: &KeyEvent) {
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_altgr_pressed() -> bool {
|
||||
let altgr_rawkey = RawKey::LinuxXorgKeycode(ControlKey::RAlt.value() as _);
|
||||
KEYS_DOWN
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&(ControlKey::RAlt.value() as _))
|
||||
.get(&KeysDown::RdevKey(altgr_rawkey))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
@@ -1011,7 +1025,7 @@ fn release_keys(en: &mut Enigo, to_release: &Vec<Key>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn record_pressed_key(record_key: u64, down: bool) {
|
||||
fn record_pressed_key(record_key: KeysDown, down: bool) {
|
||||
let mut key_down = KEYS_DOWN.lock().unwrap();
|
||||
if down {
|
||||
key_down.insert(record_key, Instant::now());
|
||||
@@ -1050,12 +1064,12 @@ fn legacy_keyboard_mode(evt: &KeyEvent) {
|
||||
return;
|
||||
}
|
||||
let record_key = ck.value() as u64;
|
||||
record_pressed_key(record_key, down);
|
||||
record_pressed_key(KeysDown::EnigoKey(record_key), down);
|
||||
process_control_key(&mut en, &ck, down)
|
||||
}
|
||||
Some(key_event::Union::Chr(chr)) => {
|
||||
let record_key = chr as u64 + KEY_CHAR_START;
|
||||
record_pressed_key(record_key, down);
|
||||
record_pressed_key(KeysDown::EnigoKey(record_key), down);
|
||||
process_chr(&mut en, chr, down)
|
||||
}
|
||||
Some(key_event::Union::Unicode(chr)) => process_unicode(&mut en, chr),
|
||||
@@ -1067,6 +1081,36 @@ fn legacy_keyboard_mode(evt: &KeyEvent) {
|
||||
release_keys(&mut en, &to_release);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn translate_process_code(code: u32, down: bool) {
|
||||
crate::platform::windows::try_change_desktop();
|
||||
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::Seq(seq)) => {
|
||||
ENIGO.lock().unwrap().key_sequence(seq);
|
||||
}
|
||||
Some(key_event::Union::Chr(..)) =>
|
||||
{
|
||||
#[cfg(target_os = "windows")]
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_key_(evt: &KeyEvent) {
|
||||
if EXITING.load(Ordering::SeqCst) {
|
||||
return;
|
||||
@@ -1080,7 +1124,7 @@ pub fn handle_key_(evt: &KeyEvent) {
|
||||
map_keyboard_mode(evt);
|
||||
}
|
||||
KeyboardMode::Translate => {
|
||||
legacy_keyboard_mode(evt);
|
||||
translate_keyboard_mode(evt);
|
||||
}
|
||||
_ => {
|
||||
legacy_keyboard_mode(evt);
|
||||
|
||||
@@ -2,9 +2,7 @@ use core::slice;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::anyhow,
|
||||
bail,
|
||||
config::Config,
|
||||
log,
|
||||
bail, libc, log,
|
||||
message_proto::{KeyEvent, MouseEvent},
|
||||
protobuf::Message,
|
||||
tokio::{self, sync::mpsc},
|
||||
@@ -15,6 +13,7 @@ use shared_memory::*;
|
||||
use std::{
|
||||
mem::size_of,
|
||||
ops::{Deref, DerefMut},
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
@@ -25,6 +24,7 @@ use winapi::{
|
||||
|
||||
use crate::{
|
||||
ipc::{self, new_listener, Connection, Data, DataPortableService},
|
||||
platform::set_path_permission,
|
||||
video_service::get_current_display,
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ impl DerefMut for SharedMemory {
|
||||
|
||||
impl SharedMemory {
|
||||
pub fn create(name: &str, size: usize) -> ResultType<Self> {
|
||||
let flink = Self::flink(name.to_string());
|
||||
let flink = Self::flink(name.to_string())?;
|
||||
let shmem = match ShmemConf::new()
|
||||
.size(size)
|
||||
.flink(&flink)
|
||||
@@ -91,12 +91,12 @@ impl SharedMemory {
|
||||
}
|
||||
};
|
||||
log::info!("Create shared memory, size:{}, flink:{}", size, flink);
|
||||
Self::set_all_perm(&flink);
|
||||
set_path_permission(&PathBuf::from(flink), "F").ok();
|
||||
Ok(SharedMemory { inner: shmem })
|
||||
}
|
||||
|
||||
pub fn open_existing(name: &str) -> ResultType<Self> {
|
||||
let flink = Self::flink(name.to_string());
|
||||
let flink = Self::flink(name.to_string())?;
|
||||
let shmem = match ShmemConf::new().flink(&flink).allow_raw(true).open() {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
@@ -116,30 +116,17 @@ impl SharedMemory {
|
||||
}
|
||||
}
|
||||
|
||||
fn flink(name: String) -> String {
|
||||
let mut shmem_flink = format!("shared_memory{}", name);
|
||||
if cfg!(windows) {
|
||||
let df = "C:\\ProgramData";
|
||||
let df = if std::path::Path::new(df).exists() {
|
||||
df.to_owned()
|
||||
} else {
|
||||
std::env::var("TEMP").unwrap_or("C:\\Windows\\TEMP".to_owned())
|
||||
};
|
||||
let df = format!("{}\\{}", df, *hbb_common::config::APP_NAME.read().unwrap());
|
||||
std::fs::create_dir(&df).ok();
|
||||
shmem_flink = format!("{}\\{}", df, shmem_flink);
|
||||
} else {
|
||||
shmem_flink = Config::ipc_path("").replace("ipc", "") + &shmem_flink;
|
||||
}
|
||||
return shmem_flink;
|
||||
}
|
||||
|
||||
fn set_all_perm(_p: &str) {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(_p, std::fs::Permissions::from_mode(0o0777)).ok();
|
||||
fn flink(name: String) -> ResultType<String> {
|
||||
let mut dir = crate::platform::user_accessible_folder()?;
|
||||
dir = dir.join(hbb_common::config::APP_NAME.read().unwrap().clone());
|
||||
if !dir.exists() {
|
||||
std::fs::create_dir(&dir)?;
|
||||
set_path_permission(&dir, "F").ok();
|
||||
}
|
||||
Ok(dir
|
||||
.join(format!("shared_memory{}", name))
|
||||
.to_string_lossy()
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +190,7 @@ mod utils {
|
||||
}
|
||||
}
|
||||
|
||||
// functions called in seperate SYSTEM user process.
|
||||
// functions called in separate SYSTEM user process.
|
||||
pub mod server {
|
||||
use super::*;
|
||||
|
||||
@@ -407,7 +394,7 @@ pub mod server {
|
||||
}
|
||||
ConnCount(Some(n)) => {
|
||||
if n == 0 {
|
||||
log::info!("Connnection count equals 0, exit");
|
||||
log::info!("Connection count equals 0, exit");
|
||||
stream.send(&Data::DataPortableService(WillClose)).await.ok();
|
||||
break;
|
||||
}
|
||||
@@ -455,14 +442,20 @@ pub mod client {
|
||||
use super::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref PORTABLE_SERVICE_RUNNING: Arc<Mutex<bool>> = Default::default();
|
||||
static ref RUNNING: Arc<Mutex<bool>> = Default::default();
|
||||
static ref SHMEM: Arc<Mutex<Option<SharedMemory>>> = Default::default();
|
||||
static ref SENDER : Mutex<mpsc::UnboundedSender<ipc::Data>> = Mutex::new(client::start_ipc_server());
|
||||
static ref QUICK_SUPPORT: Arc<Mutex<bool>> = Default::default();
|
||||
}
|
||||
|
||||
pub(crate) fn start_portable_service() -> ResultType<()> {
|
||||
pub enum StartPara {
|
||||
Direct,
|
||||
Logon(String, String),
|
||||
}
|
||||
|
||||
pub(crate) fn start_portable_service(para: StartPara) -> ResultType<()> {
|
||||
log::info!("start portable service");
|
||||
if PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
|
||||
if RUNNING.lock().unwrap().clone() {
|
||||
bail!("already running");
|
||||
}
|
||||
if SHMEM.lock().unwrap().is_none() {
|
||||
@@ -491,14 +484,60 @@ pub mod client {
|
||||
unsafe {
|
||||
libc::memset(shmem.as_ptr() as _, 0, shmem.len() as _);
|
||||
}
|
||||
if crate::platform::run_background(
|
||||
&std::env::current_exe()?.to_string_lossy().to_string(),
|
||||
"--portable-service",
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
*SHMEM.lock().unwrap() = None;
|
||||
bail!("Failed to run portable service process");
|
||||
drop(option);
|
||||
match para {
|
||||
StartPara::Direct => {
|
||||
if let Err(e) = crate::platform::run_background(
|
||||
&std::env::current_exe()?.to_string_lossy().to_string(),
|
||||
"--portable-service",
|
||||
) {
|
||||
*SHMEM.lock().unwrap() = None;
|
||||
bail!("Failed to run portable service process:{}", e);
|
||||
}
|
||||
}
|
||||
StartPara::Logon(username, password) => {
|
||||
#[allow(unused_mut)]
|
||||
let mut exe = std::env::current_exe()?.to_string_lossy().to_string();
|
||||
#[cfg(feature = "flutter")]
|
||||
{
|
||||
if let Some(dir) = PathBuf::from(&exe).parent() {
|
||||
if set_path_permission(&PathBuf::from(dir), "RX").is_err() {
|
||||
*SHMEM.lock().unwrap() = None;
|
||||
bail!("Failed to set permission of {:?}", dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
match hbb_common::directories_next::UserDirs::new() {
|
||||
Some(user_dir) => {
|
||||
let dir = user_dir
|
||||
.home_dir()
|
||||
.join("AppData")
|
||||
.join("Local")
|
||||
.join("rustdesk-sciter");
|
||||
if std::fs::create_dir_all(&dir).is_ok() {
|
||||
let dst = dir.join("rustdesk.exe");
|
||||
if std::fs::copy(&exe, &dst).is_ok() {
|
||||
if dst.exists() {
|
||||
if set_path_permission(&dir, "RX").is_ok() {
|
||||
exe = dst.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
if let Err(e) = crate::platform::windows::create_process_with_logon(
|
||||
username.as_str(),
|
||||
password.as_str(),
|
||||
&exe,
|
||||
"--portable-service",
|
||||
) {
|
||||
*SHMEM.lock().unwrap() = None;
|
||||
bail!("Failed to run portable service process:{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
let _sender = SENDER.lock().unwrap();
|
||||
Ok(())
|
||||
@@ -509,6 +548,10 @@ pub mod client {
|
||||
*SHMEM.lock().unwrap() = None;
|
||||
}
|
||||
|
||||
pub fn set_quick_support(v: bool) {
|
||||
*QUICK_SUPPORT.lock().unwrap() = v;
|
||||
}
|
||||
|
||||
pub struct CapturerPortable;
|
||||
|
||||
impl CapturerPortable {
|
||||
@@ -623,17 +666,7 @@ pub mod client {
|
||||
use DataPortableService::*;
|
||||
let rx = Arc::new(tokio::sync::Mutex::new(rx));
|
||||
let postfix = IPC_SUFFIX;
|
||||
#[cfg(feature = "flutter")]
|
||||
let quick_support = {
|
||||
let args: Vec<_> = std::env::args().collect();
|
||||
args.contains(&"--quick_support".to_string())
|
||||
};
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
let quick_support = std::env::current_exe()
|
||||
.unwrap_or("".into())
|
||||
.to_string_lossy()
|
||||
.to_lowercase()
|
||||
.ends_with("qs.exe");
|
||||
let quick_support = QUICK_SUPPORT.lock().unwrap().clone();
|
||||
|
||||
match new_listener(postfix).await {
|
||||
Ok(mut incoming) => loop {
|
||||
@@ -668,7 +701,7 @@ pub mod client {
|
||||
}
|
||||
Pong => {
|
||||
nack = 0;
|
||||
*PORTABLE_SERVICE_RUNNING.lock().unwrap() = true;
|
||||
*RUNNING.lock().unwrap() = true;
|
||||
},
|
||||
ConnCount(None) => {
|
||||
if !quick_support {
|
||||
@@ -699,7 +732,7 @@ pub mod client {
|
||||
}
|
||||
}
|
||||
}
|
||||
*PORTABLE_SERVICE_RUNNING.lock().unwrap() = false;
|
||||
*RUNNING.lock().unwrap() = false;
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -752,11 +785,11 @@ pub mod client {
|
||||
use_yuv: bool,
|
||||
portable_service_running: bool,
|
||||
) -> ResultType<Box<dyn TraitCapturer>> {
|
||||
if portable_service_running != PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
|
||||
if portable_service_running != RUNNING.lock().unwrap().clone() {
|
||||
log::info!("portable service status mismatch");
|
||||
}
|
||||
if portable_service_running {
|
||||
log::info!("Create shared memeory capturer");
|
||||
log::info!("Create shared memory capturer");
|
||||
return Ok(Box::new(CapturerPortable::new(current_display, use_yuv)));
|
||||
} else {
|
||||
log::debug!("Create capturer dxgi|gdi");
|
||||
@@ -767,7 +800,7 @@ pub mod client {
|
||||
}
|
||||
|
||||
pub fn get_cursor_info(pci: PCURSORINFO) -> BOOL {
|
||||
if PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
|
||||
if RUNNING.lock().unwrap().clone() {
|
||||
get_cursor_info_(&mut SHMEM.lock().unwrap().as_mut().unwrap(), pci)
|
||||
} else {
|
||||
unsafe { winuser::GetCursorInfo(pci) }
|
||||
@@ -775,7 +808,7 @@ pub mod client {
|
||||
}
|
||||
|
||||
pub fn handle_mouse(evt: &MouseEvent) {
|
||||
if PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
|
||||
if RUNNING.lock().unwrap().clone() {
|
||||
handle_mouse_(evt).ok();
|
||||
} else {
|
||||
crate::input_service::handle_mouse_(evt);
|
||||
@@ -783,12 +816,16 @@ pub mod client {
|
||||
}
|
||||
|
||||
pub fn handle_key(evt: &KeyEvent) {
|
||||
if PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
|
||||
if RUNNING.lock().unwrap().clone() {
|
||||
handle_key_(evt).ok();
|
||||
} else {
|
||||
crate::input_service::handle_key_(evt);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn running() -> bool {
|
||||
RUNNING.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
||||
@@ -20,14 +20,10 @@
|
||||
|
||||
use super::{video_qos::VideoQoS, *};
|
||||
#[cfg(windows)]
|
||||
use crate::portable_service::client::PORTABLE_SERVICE_RUNNING;
|
||||
#[cfg(windows)]
|
||||
use hbb_common::get_version_number;
|
||||
use hbb_common::{
|
||||
tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
Mutex as TokioMutex,
|
||||
},
|
||||
use hbb_common::tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
Mutex as TokioMutex,
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
use scrap::Capturer;
|
||||
@@ -69,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 {
|
||||
@@ -211,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(),
|
||||
@@ -309,14 +306,14 @@ pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn check_uac_switch(privacy_mode_id: i32, captuerer_privacy_mode_id: i32) -> ResultType<()> {
|
||||
if captuerer_privacy_mode_id != 0 {
|
||||
if privacy_mode_id != captuerer_privacy_mode_id {
|
||||
if !crate::ui::win_privacy::is_process_consent_running()? {
|
||||
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::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");
|
||||
}
|
||||
}
|
||||
@@ -330,7 +327,7 @@ pub(super) struct CapturerInfo {
|
||||
pub ndisplay: usize,
|
||||
pub current: usize,
|
||||
pub privacy_mode_id: i32,
|
||||
pub _captuerer_privacy_mode_id: i32,
|
||||
pub _capturer_privacy_mode_id: i32,
|
||||
pub capturer: Box<dyn TraitCapturer>,
|
||||
}
|
||||
|
||||
@@ -359,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,
|
||||
@@ -367,33 +364,34 @@ 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();
|
||||
#[cfg(not(windows))]
|
||||
let captuerer_privacy_mode_id = privacy_mode_id;
|
||||
let capturer_privacy_mode_id = privacy_mode_id;
|
||||
#[cfg(windows)]
|
||||
let mut captuerer_privacy_mode_id = privacy_mode_id;
|
||||
let mut capturer_privacy_mode_id = privacy_mode_id;
|
||||
#[cfg(windows)]
|
||||
if captuerer_privacy_mode_id != 0 {
|
||||
if crate::ui::win_privacy::is_process_consent_running()? {
|
||||
captuerer_privacy_mode_id = 0;
|
||||
if capturer_privacy_mode_id != 0 {
|
||||
if crate::win_privacy::is_process_consent_running()? {
|
||||
capturer_privacy_mode_id = 0;
|
||||
}
|
||||
}
|
||||
log::debug!(
|
||||
"Try create capturer with captuerer privacy mode id {}",
|
||||
captuerer_privacy_mode_id,
|
||||
"Try create capturer with capturer privacy mode id {}",
|
||||
capturer_privacy_mode_id,
|
||||
);
|
||||
|
||||
if privacy_mode_id != 0 {
|
||||
if privacy_mode_id != captuerer_privacy_mode_id {
|
||||
if privacy_mode_id != capturer_privacy_mode_id {
|
||||
log::info!("In privacy mode, but show UAC prompt window for now");
|
||||
} else {
|
||||
log::info!("In privacy mode, the peer side cannot watch the screen");
|
||||
}
|
||||
}
|
||||
let capturer = create_capturer(
|
||||
captuerer_privacy_mode_id,
|
||||
capturer_privacy_mode_id,
|
||||
display,
|
||||
use_yuv,
|
||||
current,
|
||||
@@ -406,11 +404,48 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
|
||||
ndisplay,
|
||||
current,
|
||||
privacy_mode_id,
|
||||
_captuerer_privacy_mode_id: captuerer_privacy_mode_id,
|
||||
_capturer_privacy_mode_id: capturer_privacy_mode_id,
|
||||
capturer,
|
||||
})
|
||||
}
|
||||
|
||||
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()?;
|
||||
@@ -419,7 +454,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
super::wayland::ensure_inited()?;
|
||||
#[cfg(windows)]
|
||||
let last_portable_service_running = PORTABLE_SERVICE_RUNNING.lock().unwrap().clone();
|
||||
let last_portable_service_running = crate::portable_service::client::running();
|
||||
#[cfg(not(windows))]
|
||||
let last_portable_service_running = false;
|
||||
|
||||
@@ -467,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();
|
||||
@@ -493,7 +536,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
|
||||
while sp.ok() {
|
||||
#[cfg(windows)]
|
||||
check_uac_switch(c.privacy_mode_id, c._captuerer_privacy_mode_id)?;
|
||||
check_uac_switch(c.privacy_mode_id, c._capturer_privacy_mode_id)?;
|
||||
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
if video_qos.check_if_updated() {
|
||||
@@ -502,7 +545,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
video_qos.target_bitrate,
|
||||
video_qos.fps
|
||||
);
|
||||
encoder.set_bitrate(video_qos.target_bitrate).unwrap();
|
||||
allow_err!(encoder.set_bitrate(video_qos.target_bitrate));
|
||||
spf = video_qos.spf();
|
||||
}
|
||||
drop(video_qos);
|
||||
@@ -518,14 +561,14 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
bail!("SWITCH");
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if last_portable_service_running != PORTABLE_SERVICE_RUNNING.lock().unwrap().clone() {
|
||||
if last_portable_service_running != crate::portable_service::client::running() {
|
||||
bail!("SWITCH");
|
||||
}
|
||||
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if crate::platform::windows::desktop_changed()
|
||||
&& !PORTABLE_SERVICE_RUNNING.lock().unwrap().clone()
|
||||
&& !crate::portable_service::client::running()
|
||||
{
|
||||
bail!("Desktop changed");
|
||||
}
|
||||
@@ -533,6 +576,19 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
let now = time::Instant::now();
|
||||
if last_check_displays.elapsed().as_millis() > 1000 {
|
||||
last_check_displays = now;
|
||||
|
||||
// Capturer on macos does not return Err event the solution is changed.
|
||||
#[cfg(target_os = "macos")]
|
||||
if check_display_changed(c.ndisplay, c.current, c.width, c.height) {
|
||||
log::info!("Displays changed");
|
||||
*SWITCH.lock().unwrap() = true;
|
||||
bail!("SWITCH");
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -602,7 +658,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
if !scrap::is_x11() {
|
||||
if would_block_count >= 100 {
|
||||
super::wayland::release_resource();
|
||||
bail!("Wayland capturer none 100 times, try restart captuere");
|
||||
bail!("Wayland capturer none 100 times, try restart capture");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -637,7 +693,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
while wait_begin.elapsed().as_millis() < timeout_millis as _ {
|
||||
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
|
||||
#[cfg(windows)]
|
||||
check_uac_switch(c.privacy_mode_id, c._captuerer_privacy_mode_id)?;
|
||||
check_uac_switch(c.privacy_mode_id, c._capturer_privacy_mode_id)?;
|
||||
frame_controller.try_wait_next(&mut fetched_conn_ids, 300);
|
||||
// break if all connections have received current frame
|
||||
if fetched_conn_ids.len() >= frame_controller.send_conn_ids.len() {
|
||||
@@ -802,11 +858,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>) {
|
||||
@@ -865,6 +917,7 @@ pub async fn switch_display(i: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh() {
|
||||
#[cfg(target_os = "android")]
|
||||
Display::refresh_size();
|
||||
@@ -892,10 +945,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()?)
|
||||
@@ -954,21 +1009,24 @@ 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();
|
||||
START.call_once(|| {
|
||||
if !crate::platform::is_installed()
|
||||
&& !crate::platform::is_root()
|
||||
&& !crate::platform::is_elevated(None).map_or(false, |b| b)
|
||||
{
|
||||
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 let Ok(elevated) = crate::platform::is_foreground_window_elevated() {
|
||||
*IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap() = elevated;
|
||||
if !crate::platform::is_elevated(None).unwrap_or(false) {
|
||||
if let Ok(elevated) = crate::platform::is_foreground_window_elevated() {
|
||||
*IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap() = elevated;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use hbb_common::{allow_err, platform::linux::DISTRO};
|
||||
use scrap::{set_map_err, Capturer, Display, Frame, TraitCapturer};
|
||||
use scrap::{is_cursor_embedded, set_map_err, Capturer, Display, Frame, TraitCapturer};
|
||||
use std::io;
|
||||
|
||||
use super::video_service::{
|
||||
@@ -12,7 +12,7 @@ lazy_static::lazy_static! {
|
||||
static ref LOG_SCRAP_COUNT: Mutex<u32> = Mutex::new(0);
|
||||
}
|
||||
|
||||
pub fn set_wayland_scrap_map_err() {
|
||||
pub fn init() {
|
||||
set_map_err(map_err_scrap);
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ pub(super) async fn check_init() -> ResultType<()> {
|
||||
let num = all.len();
|
||||
let (primary, mut displays) = super::video_service::get_displays_2(&all);
|
||||
for display in displays.iter_mut() {
|
||||
display.cursor_embedded = true;
|
||||
display.cursor_embedded = is_cursor_embedded();
|
||||
}
|
||||
|
||||
let mut rects: Vec<((i32, i32), usize, usize)> = Vec::new();
|
||||
@@ -276,7 +276,7 @@ pub(super) fn get_capturer() -> ResultType<super::video_service::CapturerInfo> {
|
||||
ndisplay: cap_display_info.num,
|
||||
current: cap_display_info.current,
|
||||
privacy_mode_id: 0,
|
||||
_captuerer_privacy_mode_id: 0,
|
||||
_capturer_privacy_mode_id: 0,
|
||||
capturer: Box::new(cap_display_info.capturer.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
232
src/tray.rs
232
src/tray.rs
@@ -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: >k::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,33 +89,90 @@ fn is_service_stopped() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn make_tray() {
|
||||
use tray_item::TrayItem;
|
||||
let mode = dark_light::detect();
|
||||
let icon_path;
|
||||
match mode {
|
||||
dark_light::Mode::Dark => {
|
||||
icon_path = "mac-tray-light.png";
|
||||
}
|
||||
dark_light::Mode::Light => {
|
||||
icon_path = "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();
|
||||
|
||||
unsafe {
|
||||
crate::platform::delegate::set_delegate(None);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
13
src/ui/cm.rs
13
src/ui/cm.rs
@@ -55,6 +55,17 @@ impl InvokeUiCM for SciterHandler {
|
||||
fn show_elevation(&self, show: bool) {
|
||||
self.call("showElevation", &make_args!(show));
|
||||
}
|
||||
|
||||
fn update_voice_call_state(&self, client: &crate::ui_cm_interface::Client) {
|
||||
self.call(
|
||||
"updateVoiceCallState",
|
||||
&make_args!(
|
||||
client.id,
|
||||
client.in_voice_call,
|
||||
client.incoming_voice_call
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl SciterHandler {
|
||||
@@ -89,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) {
|
||||
|
||||
@@ -31,7 +31,7 @@ class Body: Reactor.Component
|
||||
var disconnected = c.disconnected;
|
||||
var show_elevation_btn = handler.can_elevate() && show_elevation && !c.is_file_transfer && c.port_forward.length == 0;
|
||||
var show_accept_btn = handler.get_option('approve-mode') != 'password';
|
||||
// below size:* is work around for Linux, it already set in css, but not work, shit sciter
|
||||
// below size:* is a workaround for Linux, it already set in css, but not work, shit sciter
|
||||
return <div .content style="size:*">
|
||||
<div .left-panel>
|
||||
<div .icon-and-id>
|
||||
@@ -42,7 +42,7 @@ class Body: Reactor.Component
|
||||
<div .id style="font-weight: bold; font-size: 1.2em;">{c.name}</div>
|
||||
<div .id>({c.peer_id})</div>
|
||||
<div style="margin-top: 1.2em">{auth
|
||||
? <span>{disconnected ? translate('Disconnected') : translate('Connected')}{" "}<span #time>{getElaspsed(c.time, c.now)}</span></span>
|
||||
? <span>{disconnected ? translate('Disconnected') : translate('Connected')}{" "}<span #time>{getElapsed(c.time, c.now)}</span></span>
|
||||
: <span>{translate('Request access to your device')}{"..."}</span>}
|
||||
</div>
|
||||
</div>
|
||||
@@ -442,7 +442,7 @@ function self.ready() {
|
||||
view.move(sw - w, 0, w, h);
|
||||
}
|
||||
|
||||
function getElaspsed(time, now) {
|
||||
function getElapsed(time, now) {
|
||||
var seconds = Date.diff(time, now, #seconds);
|
||||
var hours = seconds / 3600;
|
||||
var days = hours / 24;
|
||||
@@ -482,7 +482,7 @@ function updateTime() {
|
||||
if (el) {
|
||||
var c = connections[body.cur];
|
||||
if (c && c.authorized && !c.disconnected) {
|
||||
el.text = getElaspsed(c.time, c.now);
|
||||
el.text = getElapsed(c.time, c.now);
|
||||
}
|
||||
}
|
||||
updateTime();
|
||||
|
||||
@@ -198,6 +198,7 @@ class Header: Reactor.Component {
|
||||
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
|
||||
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>{translate('Lock after session end')}</li> : ""}
|
||||
{keyboard_enabled && pi.platform == "Windows" ? <li #privacy-mode><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
|
||||
{keyboard_enabled && ((is_osx && pi.platform != "Mac OS") || (!is_osx && pi.platform == "Mac OS")) ? <li #allow_swap_key .toggle-option><span>{svg_checkmark}</span>{translate('Swap control-command key')}</li> : ""}
|
||||
</menu>
|
||||
</popup>;
|
||||
}
|
||||
@@ -378,7 +379,7 @@ class Header: Reactor.Component {
|
||||
togglePrivacyMode(me.id);
|
||||
} else if (me.id == "show-quality-monitor") {
|
||||
toggleQualityMonitor(me.id);
|
||||
}else if (me.attributes.hasClass("toggle-option")) {
|
||||
} else if (me.attributes.hasClass("toggle-option")) {
|
||||
handler.toggle_option(me.id);
|
||||
toggleMenuState();
|
||||
} else if (!me.attributes.hasClass("selected")) {
|
||||
@@ -440,7 +441,7 @@ function toggleMenuState() {
|
||||
for (var el in $$(menu#keyboard-options>li)) {
|
||||
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
|
||||
}
|
||||
for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) {
|
||||
for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end", "allow_swap_key"]) {
|
||||
var el = self.select('#' + id);
|
||||
if (el) {
|
||||
var value = handler.get_toggle_option(id);
|
||||
@@ -480,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) {
|
||||
|
||||
@@ -9,7 +9,6 @@ var app;
|
||||
var tmp = handler.get_connect_status();
|
||||
var connect_status = tmp[0];
|
||||
var service_stopped = handler.get_option("stop-service") == "Y";
|
||||
var rendezvous_service_stopped = false;
|
||||
var using_public_server = handler.using_public_server();
|
||||
var software_update_url = "";
|
||||
var key_confirmed = tmp[1];
|
||||
@@ -63,12 +62,15 @@ function createNewConnect(id, type) {
|
||||
id = id.replace(/\s/g, "");
|
||||
app.remote_id.value = formatId(id);
|
||||
if (!id) return;
|
||||
var old_id = id;
|
||||
id = handler.handle_relay_id(id);
|
||||
var force_relay = old_id != id;
|
||||
if (id == my_id) {
|
||||
msgbox("custom-error", "Error", "You cannot connect to your own computer");
|
||||
return;
|
||||
}
|
||||
handler.set_remote_id(id);
|
||||
handler.new_remote(id, type);
|
||||
handler.new_remote(id, type, force_relay);
|
||||
}
|
||||
|
||||
class ShareRdp: Reactor.Component {
|
||||
@@ -310,7 +312,6 @@ class MyIdMenu: Reactor.Component {
|
||||
{handler.is_rdp_service_open() ? <ShareRdp /> : ""}
|
||||
<DirectServer />
|
||||
{false && handler.using_public_server() && <li #allow-always-relay><span>{svg_checkmark}</span>{translate('Always connected via relay')}</li>}
|
||||
{handler.has_rendezvous_service() ? <li #stop-rendezvous-service>{translate(rendezvous_service_stopped ? "Start ID/relay service" : "Stop ID/relay service")}</li> : ""}
|
||||
{handler.is_ok_change_id() ? <div .separator /> : ""}
|
||||
{username ?
|
||||
<li #logout>{translate('Logout')} ({username})</li> :
|
||||
@@ -468,8 +469,6 @@ class MyIdMenu: Reactor.Component {
|
||||
}, 240);
|
||||
} else if (me.id == "stop-service") {
|
||||
handler.set_option("stop-service", service_stopped ? "" : "Y");
|
||||
} else if (me.id == "stop-rendezvous-service") {
|
||||
handler.set_option("stop-rendezvous-service", rendezvous_service_stopped ? "" : "Y");
|
||||
} else if (me.id == "change-id") {
|
||||
msgbox("custom-id", translate("Change ID"), "<div .form .set-password> \
|
||||
<div>" + translate('id_change_tip') + " </div> \
|
||||
@@ -759,11 +758,6 @@ class FixWayland: Reactor.Component {
|
||||
</div>;
|
||||
}
|
||||
|
||||
event click $(#fix-wayland) {
|
||||
handler.fix_login_wayland();
|
||||
app.update();
|
||||
}
|
||||
|
||||
event click $(#help-me) {
|
||||
handler.open_url(translate("doc_fix_wayland"));
|
||||
}
|
||||
@@ -773,19 +767,11 @@ class ModifyDefaultLogin: Reactor.Component {
|
||||
function render() {
|
||||
return <div .trust-me>
|
||||
<div>{translate('Warning')}</div>
|
||||
<div>{translate('Current Wayland display server is not supported')}</div>
|
||||
<div>{translate('wayland_experiment_tip')}</div>
|
||||
<div #help-me .link>{translate('Help')}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
event click $(#modify-default-login) {
|
||||
if (var r = handler.modify_default_login()) {
|
||||
// without handler, will fail, fucking stupid sciter
|
||||
handler.msgbox("custom-error", "Error", r);
|
||||
}
|
||||
app.update();
|
||||
}
|
||||
|
||||
event click $(#help-me) {
|
||||
handler.open_url(translate("doc_fix_wayland"));
|
||||
}
|
||||
@@ -1121,11 +1107,6 @@ function checkConnectStatus() {
|
||||
service_stopped = tmp;
|
||||
app.update();
|
||||
}
|
||||
tmp = !!handler.get_option("stop-rendezvous-service");
|
||||
if (tmp != rendezvous_service_stopped) {
|
||||
rendezvous_service_stopped = tmp;
|
||||
myIdMenu.update();
|
||||
}
|
||||
tmp = handler.using_public_server();
|
||||
if (tmp != using_public_server) {
|
||||
using_public_server = tmp;
|
||||
|
||||
@@ -13,7 +13,7 @@ class Install: Reactor.Component {
|
||||
</div>
|
||||
<div><button|checkbox #startmenu checked>{translate('Create start menu shortcuts')}</button></div>
|
||||
<div><button|checkbox #desktopicon checked>{translate('Create desktop icon')}</button></div>
|
||||
<div #aggrement .link style="margin-top: 2em;">{translate('End-user license agreement')}</div>
|
||||
<div #agreement .link style="margin-top: 2em;">{translate('End-user license agreement')}</div>
|
||||
<div>{translate('agreement_tip')}</div>
|
||||
<div style="height: 1px; background: gray; margin-top: 1em" />
|
||||
<div style="text-align: right;">
|
||||
@@ -46,7 +46,7 @@ class Install: Reactor.Component {
|
||||
}
|
||||
}
|
||||
|
||||
event click $(#aggrement) {
|
||||
event click $(#agreement) {
|
||||
view.open_url("http://rustdesk.com/privacy");
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ div#quality-monitor {
|
||||
padding: 5px;
|
||||
min-width: 150px;
|
||||
color: azure;
|
||||
border: solid azure;
|
||||
border: 0.5px solid azure;
|
||||
}
|
||||
|
||||
video#handler {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{Arc, Mutex},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
use sciter::{
|
||||
@@ -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 => {}
|
||||
@@ -264,6 +274,33 @@ impl InvokeUiSession for SciterHandler {
|
||||
fn update_block_input_state(&self, on: bool) {
|
||||
self.call("updateBlockInputState", &make_args!(on));
|
||||
}
|
||||
|
||||
fn switch_back(&self, _id: &str) {}
|
||||
|
||||
fn portable_service_running(&self, _running: bool) {}
|
||||
|
||||
fn on_voice_call_started(&self) {
|
||||
self.call("onVoiceCallStart", &make_args!());
|
||||
}
|
||||
|
||||
fn on_voice_call_closed(&self, reason: &str) {
|
||||
self.call("onVoiceCallClosed", &make_args!(reason));
|
||||
}
|
||||
|
||||
fn on_voice_call_waiting(&self) {
|
||||
self.call("onVoiceCallWaiting", &make_args!());
|
||||
}
|
||||
|
||||
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>);
|
||||
@@ -321,7 +358,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 => {
|
||||
@@ -370,7 +407,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();
|
||||
@@ -418,15 +455,21 @@ impl sciter::EventHandler for SciterSession {
|
||||
fn supported_hwcodec();
|
||||
fn change_prefer_codec();
|
||||
fn restart_remote_device();
|
||||
fn request_voice_call();
|
||||
fn close_voice_call();
|
||||
}
|
||||
}
|
||||
|
||||
impl SciterSession {
|
||||
pub fn new(cmd: String, id: String, password: String, args: Vec<String>) -> Self {
|
||||
let force_relay = args.contains(&"--relay".to_string());
|
||||
let session: Session<SciterHandler> = Session {
|
||||
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()
|
||||
};
|
||||
|
||||
@@ -440,7 +483,11 @@ impl SciterSession {
|
||||
ConnType::DEFAULT_CONN
|
||||
};
|
||||
|
||||
session.lc.write().unwrap().initialize(id, conn_type);
|
||||
session
|
||||
.lc
|
||||
.write()
|
||||
.unwrap()
|
||||
.initialize(id, conn_type, None, force_relay);
|
||||
|
||||
Self(session)
|
||||
}
|
||||
@@ -466,7 +513,7 @@ impl SciterSession {
|
||||
}
|
||||
|
||||
pub fn get_icon(&self) -> String {
|
||||
crate::get_icon()
|
||||
super::get_icon()
|
||||
}
|
||||
|
||||
fn supported_hwcodec(&self) -> Value {
|
||||
|
||||
@@ -120,7 +120,7 @@ function resetWheel() {
|
||||
|
||||
var INERTIA_ACCELERATION = 30;
|
||||
|
||||
// not good, precision not enough to simulate accelation effect,
|
||||
// not good, precision not enough to simulate acceleration effect,
|
||||
// seems have to use pixel based rather line based delta
|
||||
function accWheel(v, is_x) {
|
||||
if (wheeling) return;
|
||||
|
||||
@@ -48,6 +48,9 @@ pub struct Client {
|
||||
pub file: bool,
|
||||
pub restart: bool,
|
||||
pub recording: bool,
|
||||
pub from_switch: bool,
|
||||
pub in_voice_call: bool,
|
||||
pub incoming_voice_call: bool,
|
||||
#[serde(skip)]
|
||||
tx: UnboundedSender<Data>,
|
||||
}
|
||||
@@ -87,6 +90,8 @@ pub trait InvokeUiCM: Send + Clone + 'static + Sized {
|
||||
fn change_language(&self);
|
||||
|
||||
fn show_elevation(&self, show: bool);
|
||||
|
||||
fn update_voice_call_state(&self, client: &Client);
|
||||
}
|
||||
|
||||
impl<T: InvokeUiCM> Deref for ConnectionManager<T> {
|
||||
@@ -118,6 +123,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
file: bool,
|
||||
restart: bool,
|
||||
recording: bool,
|
||||
from_switch: bool,
|
||||
tx: mpsc::UnboundedSender<Data>,
|
||||
) {
|
||||
let client = Client {
|
||||
@@ -134,7 +140,10 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
file,
|
||||
restart,
|
||||
recording,
|
||||
from_switch,
|
||||
tx,
|
||||
in_voice_call: false,
|
||||
incoming_voice_call: false
|
||||
};
|
||||
CLIENTS
|
||||
.write()
|
||||
@@ -177,6 +186,30 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
fn show_elevation(&self, show: bool) {
|
||||
self.ui_handler.show_elevation(show);
|
||||
}
|
||||
|
||||
fn voice_call_started(&self, id: i32) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
client.incoming_voice_call = false;
|
||||
client.in_voice_call = true;
|
||||
self.ui_handler.update_voice_call_state(client);
|
||||
}
|
||||
}
|
||||
|
||||
fn voice_call_incoming(&self, id: i32) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
client.incoming_voice_call = true;
|
||||
client.in_voice_call = false;
|
||||
self.ui_handler.update_voice_call_state(client);
|
||||
}
|
||||
}
|
||||
|
||||
fn voice_call_closed(&self, id: i32, _reason: &str) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
client.incoming_voice_call = false;
|
||||
client.in_voice_call = false;
|
||||
self.ui_handler.update_voice_call_state(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -241,6 +274,14 @@ pub fn get_clients_length() -> usize {
|
||||
clients.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter")]
|
||||
pub fn switch_back(id: i32) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::SwitchSidesBack));
|
||||
};
|
||||
}
|
||||
|
||||
impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
#[cfg(windows)]
|
||||
async fn enable_cliprdr_file_context(&mut self, conn_id: i32, enabled: bool) {
|
||||
@@ -253,7 +294,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
if !pre_enabled && ContextSend::is_enabled() {
|
||||
allow_err!(
|
||||
self.stream
|
||||
.send(&Data::ClipbaordFile(clipboard::ClipbaordFile::MonitorReady))
|
||||
.send(&Data::ClipboardFile(clipboard::ClipboardFile::MonitorReady))
|
||||
.await
|
||||
);
|
||||
}
|
||||
@@ -288,7 +329,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
rx_clip = rx_clip1.lock().await;
|
||||
} else {
|
||||
let rx_clip2;
|
||||
(_tx_clip, rx_clip2) = unbounded_channel::<clipboard::ClipbaordFile>();
|
||||
(_tx_clip, rx_clip2) = unbounded_channel::<clipboard::ClipboardFile>();
|
||||
rx_clip1 = Arc::new(TokioMutex::new(rx_clip2));
|
||||
rx_clip = rx_clip1.lock().await;
|
||||
}
|
||||
@@ -308,9 +349,9 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled: _file_transfer_enabled, restart, recording} => {
|
||||
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled: _file_transfer_enabled, restart, recording, from_switch} => {
|
||||
log::debug!("conn_id: {}", id);
|
||||
self.cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, recording, self.tx.clone());
|
||||
self.cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, recording, from_switch,self.tx.clone());
|
||||
self.authorized = authorized;
|
||||
self.conn_id = id;
|
||||
#[cfg(windows)]
|
||||
@@ -354,7 +395,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Data::ClipbaordFile(_clip) => {
|
||||
Data::ClipboardFile(_clip) => {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let conn_id = self.conn_id;
|
||||
@@ -378,6 +419,15 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
Data::DataPortableService(ipc::DataPortableService::CmShowElevation(show)) => {
|
||||
self.cm.show_elevation(show);
|
||||
}
|
||||
Data::StartVoiceCall => {
|
||||
self.cm.voice_call_started(self.conn_id);
|
||||
}
|
||||
Data::VoiceCallIncoming => {
|
||||
self.cm.voice_call_incoming(self.conn_id);
|
||||
}
|
||||
Data::CloseVoiceCall(reason) => {
|
||||
self.cm.voice_call_closed(self.conn_id, reason.as_str());
|
||||
}
|
||||
_ => {
|
||||
|
||||
}
|
||||
@@ -394,7 +444,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
clip_file = rx_clip.recv() => match clip_file {
|
||||
Some(_clip) => {
|
||||
#[cfg(windows)]
|
||||
allow_err!(self.tx.send(Data::ClipbaordFile(_clip)));
|
||||
allow_err!(self.tx.send(Data::ClipboardFile(_clip)));
|
||||
}
|
||||
None => {
|
||||
//
|
||||
@@ -438,16 +488,13 @@ pub async fn start_ipc<T: InvokeUiCM>(cm: ConnectionManager<T>) {
|
||||
#[cfg(windows)]
|
||||
std::thread::spawn(move || {
|
||||
log::info!("try create privacy mode window");
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Err(e) = crate::platform::windows::check_update_broker_process() {
|
||||
log::warn!(
|
||||
"Failed to check update broker process. Privacy mode may not work properly. {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
if let Err(e) = crate::platform::windows::check_update_broker_process() {
|
||||
log::warn!(
|
||||
"Failed to check update broker process. Privacy mode may not work properly. {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
allow_err!(crate::ui::win_privacy::start());
|
||||
allow_err!(crate::win_privacy::start());
|
||||
});
|
||||
|
||||
match ipc::new_listener("_cm").await {
|
||||
@@ -498,6 +545,7 @@ pub async fn start_listen<T: InvokeUiCM>(
|
||||
file,
|
||||
restart,
|
||||
recording,
|
||||
from_switch,
|
||||
..
|
||||
}) => {
|
||||
current_id = id;
|
||||
@@ -514,6 +562,7 @@ pub async fn start_listen<T: InvokeUiCM>(
|
||||
file,
|
||||
restart,
|
||||
recording,
|
||||
from_switch,
|
||||
tx.clone(),
|
||||
);
|
||||
}
|
||||
@@ -779,13 +828,7 @@ fn cm_inner_send(id: i32, data: Data) {
|
||||
|
||||
pub fn can_elevate() -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
return !crate::platform::is_installed()
|
||||
&& !crate::portable_service::client::PORTABLE_SERVICE_RUNNING
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone();
|
||||
}
|
||||
return !crate::platform::is_installed();
|
||||
#[cfg(not(windows))]
|
||||
return false;
|
||||
}
|
||||
@@ -801,3 +844,19 @@ pub fn elevate_portable(_id: i32) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
#[inline]
|
||||
pub fn handle_incoming_voice_call(id: i32, accept: bool) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
allow_err!(client.tx.send(Data::VoiceCallResponse(accept)));
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
#[inline]
|
||||
pub fn close_voice_call(id: i32) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
allow_err!(client.tx.send(Data::CloseVoiceCall("".to_owned())));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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"]));
|
||||
@@ -134,13 +111,6 @@ pub fn show_run_without_install() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_rendezvous_service() -> bool {
|
||||
#[cfg(all(windows, feature = "hbbs"))]
|
||||
return crate::platform::is_win_server() && crate::platform::windows::get_license().is_some();
|
||||
return false;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_license() -> String {
|
||||
#[cfg(windows)]
|
||||
@@ -158,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())
|
||||
}
|
||||
@@ -243,7 +213,8 @@ pub fn set_peer_option(id: String, name: String, value: String) {
|
||||
|
||||
#[inline]
|
||||
pub fn using_public_server() -> bool {
|
||||
crate::get_custom_rendezvous_server(get_option_("custom-rendezvous-server")).is_empty()
|
||||
option_env!("RENDEZVOUS_SERVER").unwrap_or("").is_empty()
|
||||
&& crate::get_custom_rendezvous_server(get_option_("custom-rendezvous-server")).is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -324,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;
|
||||
}
|
||||
}
|
||||
@@ -425,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();
|
||||
@@ -513,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")]
|
||||
@@ -583,6 +491,7 @@ pub fn is_installed_daemon(_prompt: bool) -> bool {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter")]
|
||||
pub fn is_can_input_monitoring(_prompt: bool) -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
return crate::platform::macos::is_can_input_monitoring(_prompt);
|
||||
@@ -602,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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -619,12 +528,6 @@ pub fn is_login_wayland() -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fix_login_wayland() {
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::platform::linux::fix_login_wayland();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_is_wayland() -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -633,19 +536,6 @@ pub fn current_is_wayland() -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn modify_default_login() -> String {
|
||||
#[cfg(target_os = "linux")]
|
||||
return crate::platform::linux::modify_default_login();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
#[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())
|
||||
@@ -662,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();
|
||||
}
|
||||
|
||||
@@ -734,24 +597,15 @@ pub fn get_lan_peers() -> Vec<HashMap<&'static str, String>> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_uuid() -> String {
|
||||
base64::encode(hbb_common::get_uuid())
|
||||
pub fn remove_discovered(id: String) {
|
||||
let mut peers = config::LanPeers::load().peers;
|
||||
peers.retain(|x| x.id != id);
|
||||
config::LanPeers::store(&peers);
|
||||
}
|
||||
|
||||
#[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());
|
||||
pub fn get_uuid() -> String {
|
||||
base64::encode(hbb_common::get_uuid())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
@@ -775,23 +629,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()
|
||||
@@ -832,11 +674,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(
|
||||
@@ -853,14 +690,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 {
|
||||
@@ -878,10 +707,10 @@ pub fn is_root() -> bool {
|
||||
pub fn check_super_user_permission() -> bool {
|
||||
#[cfg(feature = "flatpak")]
|
||||
return true;
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
|
||||
return crate::platform::check_super_user_permission().unwrap_or(false);
|
||||
#[cfg(not(any(windows, target_os = "linux")))]
|
||||
true
|
||||
#[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
|
||||
return true;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -936,7 +765,19 @@ pub fn account_auth_result() -> String {
|
||||
serde_json::to_string(&account::OidcSession::get_result()).unwrap_or_default()
|
||||
}
|
||||
|
||||
// notice: avoiding create ipc connecton repeatly,
|
||||
#[cfg(feature = "flutter")]
|
||||
pub fn set_user_default_option(key: String, value: String) {
|
||||
use hbb_common::config::UserDefaultConfig;
|
||||
UserDefaultConfig::load().set(key, value);
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
pub fn get_user_default_option(key: String) -> String {
|
||||
use hbb_common::config::UserDefaultConfig;
|
||||
UserDefaultConfig::load().get(&key)
|
||||
}
|
||||
|
||||
// notice: avoiding create ipc connection repeatedly,
|
||||
// because windows named pipe has serious memory leak issue.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc::Data>) {
|
||||
@@ -1129,3 +970,12 @@ async fn check_id(
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
// if it's relay id, return id processed, otherwise return original id
|
||||
pub fn handle_relay_id(id: String) -> String {
|
||||
if id.ends_with(r"\r") || id.ends_with(r"/r") {
|
||||
id[0..id.len() - 2].to_string()
|
||||
} else {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
#[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::{
|
||||
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::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::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::GrabState;
|
||||
use crate::common::{self, GrabState};
|
||||
use crate::keyboard;
|
||||
use crate::{client::Data, client::Interface};
|
||||
use async_trait::async_trait;
|
||||
use hbb_common::config::{Config, LocalConfig, PeerConfig};
|
||||
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 rdev::{Event, EventType::*};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
pub static IS_IN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@@ -29,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()
|
||||
@@ -74,6 +113,10 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.lc.read().unwrap().custom_image_quality.clone()
|
||||
}
|
||||
|
||||
pub fn get_peer_version(&self) -> i64 {
|
||||
self.lc.read().unwrap().version.clone()
|
||||
}
|
||||
|
||||
pub fn get_keyboard_mode(&self) -> String {
|
||||
self.lc.read().unwrap().keyboard_mode.clone()
|
||||
}
|
||||
@@ -116,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()));
|
||||
}
|
||||
@@ -225,6 +274,11 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
crate::platform::is_xfce()
|
||||
}
|
||||
|
||||
pub fn get_supported_keyboard_modes(&self) -> Vec<KeyboardMode> {
|
||||
let version = self.get_peer_version();
|
||||
common::get_supported_keyboard_modes(version)
|
||||
}
|
||||
|
||||
pub fn remove_port_forward(&self, port: i32) {
|
||||
let mut config = self.load_config();
|
||||
config.port_forwards = config
|
||||
@@ -319,10 +373,87 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
pub fn swab_modifier_key(&self, msg: &mut KeyEvent) {
|
||||
|
||||
let allow_swap_key = self.get_toggle_option("allow_swap_key".to_string());
|
||||
if allow_swap_key {
|
||||
if let Some(key_event::Union::ControlKey(ck)) = msg.union {
|
||||
let ck = ck.enum_value_or_default();
|
||||
let ck = match ck {
|
||||
ControlKey::Control => ControlKey::Meta,
|
||||
ControlKey::Meta => ControlKey::Control,
|
||||
ControlKey::RControl => ControlKey::Meta,
|
||||
ControlKey::RWin => ControlKey::Control,
|
||||
_ => ck,
|
||||
};
|
||||
msg.set_control_key(ck);
|
||||
}
|
||||
msg.modifiers = msg.modifiers.iter().map(|ck| {
|
||||
let ck = ck.enum_value_or_default();
|
||||
let ck = match ck {
|
||||
ControlKey::Control => ControlKey::Meta,
|
||||
ControlKey::Meta => ControlKey::Control,
|
||||
ControlKey::RControl => ControlKey::Meta,
|
||||
ControlKey::RWin => ControlKey::Control,
|
||||
_ => ck,
|
||||
};
|
||||
hbb_common::protobuf::EnumOrUnknown::new(ck)
|
||||
}).collect();
|
||||
|
||||
|
||||
let code = msg.chr();
|
||||
if code != 0 {
|
||||
let mut peer = self.peer_platform().to_lowercase();
|
||||
peer.retain(|c| !c.is_whitespace());
|
||||
|
||||
let key = match peer.as_str() {
|
||||
"windows" => {
|
||||
let key = rdev::win_key_from_scancode(code);
|
||||
let key = match key {
|
||||
rdev::Key::ControlLeft => rdev::Key::MetaLeft,
|
||||
rdev::Key::MetaLeft => rdev::Key::ControlLeft,
|
||||
rdev::Key::ControlRight => rdev::Key::MetaLeft,
|
||||
rdev::Key::MetaRight => rdev::Key::ControlLeft,
|
||||
_ => key,
|
||||
};
|
||||
rdev::win_scancode_from_key(key).unwrap_or_default()
|
||||
}
|
||||
"macos" => {
|
||||
let key = rdev::macos_key_from_code(code);
|
||||
let key = match key {
|
||||
rdev::Key::ControlLeft => rdev::Key::MetaLeft,
|
||||
rdev::Key::MetaLeft => rdev::Key::ControlLeft,
|
||||
rdev::Key::ControlRight => rdev::Key::MetaLeft,
|
||||
rdev::Key::MetaRight => rdev::Key::ControlLeft,
|
||||
_ => key,
|
||||
};
|
||||
rdev::macos_keycode_from_key(key).unwrap_or_default()
|
||||
}
|
||||
_ => {
|
||||
let key = rdev::linux_key_from_code(code);
|
||||
let key = match key {
|
||||
rdev::Key::ControlLeft => rdev::Key::MetaLeft,
|
||||
rdev::Key::MetaLeft => rdev::Key::ControlLeft,
|
||||
rdev::Key::ControlRight => rdev::Key::MetaLeft,
|
||||
rdev::Key::MetaRight => rdev::Key::ControlLeft,
|
||||
_ => key,
|
||||
};
|
||||
rdev::linux_keycode_from_key(key).unwrap_or_default()
|
||||
}
|
||||
};
|
||||
msg.set_chr(key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn send_key_event(&self, evt: &KeyEvent) {
|
||||
// mode: legacy(0), map(1), translate(2), auto(3)
|
||||
|
||||
let mut msg = evt.clone();
|
||||
self.swab_modifier_key(&mut msg);
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_key_event(evt.clone());
|
||||
msg_out.set_key_event(msg);
|
||||
self.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
@@ -349,11 +480,24 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
|
||||
pub fn enter(&self) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
match &self.lc.read().unwrap().keyboard_mode as _ {
|
||||
"legacy" => rdev::set_get_key_unicode(true),
|
||||
"translate" => rdev::set_get_key_unicode(true),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
IS_IN.store(true, Ordering::SeqCst);
|
||||
keyboard::client::change_grab_status(GrabState::Run);
|
||||
}
|
||||
|
||||
pub fn leave(&self) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
rdev::set_get_key_unicode(false);
|
||||
}
|
||||
IS_IN.store(false, Ordering::SeqCst);
|
||||
keyboard::client::change_grab_status(GrabState::Wait);
|
||||
}
|
||||
@@ -391,9 +535,10 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
|
||||
pub fn handle_flutter_key_event(
|
||||
&self,
|
||||
name: &str,
|
||||
_name: &str,
|
||||
keycode: i32,
|
||||
scancode: i32,
|
||||
lock_modes: i32,
|
||||
down_or_up: bool,
|
||||
) {
|
||||
if scancode < 0 || keycode < 0 {
|
||||
@@ -414,13 +559,13 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
KeyRelease(key)
|
||||
};
|
||||
let event = Event {
|
||||
time: std::time::SystemTime::now(),
|
||||
name: Option::Some(name.to_owned()),
|
||||
time: SystemTime::now(),
|
||||
unicode: None,
|
||||
code: keycode as _,
|
||||
scan_code: scancode as _,
|
||||
event_type: event_type,
|
||||
};
|
||||
keyboard::client::process_event(&event);
|
||||
keyboard::client::process_event(&event, Some(lock_modes));
|
||||
}
|
||||
|
||||
// flutter only TODO new input
|
||||
@@ -501,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 || {
|
||||
@@ -598,6 +747,100 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
self.update_transfer_list();
|
||||
}
|
||||
|
||||
pub fn elevate_direct(&self) {
|
||||
self.send(Data::ElevateDirect);
|
||||
}
|
||||
|
||||
pub fn elevate_with_logon(&self, username: String, password: String) {
|
||||
self.send(Data::ElevateWithLogon(username, password));
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn switch_sides(&self) {
|
||||
match crate::ipc::connect(1000, "").await {
|
||||
Ok(mut conn) => {
|
||||
if conn
|
||||
.send(&crate::ipc::Data::SwitchSidesRequest(self.id.to_string()))
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
if let Ok(Some(data)) = conn.next_timeout(1000).await {
|
||||
match data {
|
||||
crate::ipc::Data::SwitchSidesRequest(str_uuid) => {
|
||||
if let Ok(uuid) = Uuid::from_str(&str_uuid) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_switch_sides_request(SwitchSidesRequest {
|
||||
uuid: Bytes::from(uuid.as_bytes().to_vec()),
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
self.send(Data::Message(msg_out));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::info!("server not started (will try to start): {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -607,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);
|
||||
@@ -632,11 +876,19 @@ 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);
|
||||
fn cancel_msgbox(&self, tag: &str);
|
||||
fn switch_back(&self, id: &str);
|
||||
fn portable_service_running(&self, running: bool);
|
||||
fn on_voice_call_started(&self);
|
||||
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> {
|
||||
@@ -726,6 +978,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)]
|
||||
@@ -758,14 +1011,31 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
handle_test_delay(t, peer).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn swap_modifier_mouse(&self, msg : &mut hbb_common::protos::message::MouseEvent) {
|
||||
let allow_swap_key = self.get_toggle_option("allow_swap_key".to_string());
|
||||
if allow_swap_key {
|
||||
msg.modifiers = msg.modifiers.iter().map(|ck| {
|
||||
let ck = ck.enum_value_or_default();
|
||||
let ck = match ck {
|
||||
ControlKey::Control => ControlKey::Meta,
|
||||
ControlKey::Meta => ControlKey::Control,
|
||||
ControlKey::RControl => ControlKey::Meta,
|
||||
ControlKey::RWin => ControlKey::Control,
|
||||
_ => ck,
|
||||
};
|
||||
hbb_common::protobuf::EnumOrUnknown::new(ck)
|
||||
}).collect();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InvokeUiSession> Session<T> {
|
||||
pub fn lock_screen(&self) {
|
||||
crate::keyboard::client::lock_screen();
|
||||
self.send_key_event(&crate::keyboard::client::event_lock_screen());
|
||||
}
|
||||
pub fn ctrl_alt_del(&self) {
|
||||
crate::keyboard::client::ctrl_alt_del();
|
||||
self.send_key_event(&crate::keyboard::client::event_ctrl_alt_del());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,6 +1049,9 @@ pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) {
|
||||
if key.is_empty() {
|
||||
key = crate::platform::get_license_key();
|
||||
}
|
||||
if key.is_empty() && !option_env!("RENDEZVOUS_SERVER").unwrap_or("").is_empty() {
|
||||
key = RS_PUB_KEY.to_owned();
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if handler.is_port_forward() {
|
||||
if handler.is_rdp() {
|
||||
@@ -868,7 +1141,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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user