mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-03-28 07:31:01 +03:00
Merge branch 'master' into master
This commit is contained in:
87
src/api.rs
Normal file
87
src/api.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use std::ffi::{c_char};
|
||||
|
||||
use crate::{
|
||||
flutter::{FlutterHandler, SESSIONS},
|
||||
plugins::PLUGIN_REGISTRAR,
|
||||
ui_session_interface::Session,
|
||||
};
|
||||
|
||||
// API provided by RustDesk.
|
||||
pub type LoadPluginFunc = fn(*const c_char) -> i32;
|
||||
pub type UnloadPluginFunc = fn(*const c_char) -> i32;
|
||||
pub type AddSessionFunc = fn(session_id: String) -> bool;
|
||||
pub type RemoveSessionFunc = fn(session_id: &String) -> bool;
|
||||
pub type AddSessionHookFunc = fn(session_id: String, key: String, hook: SessionHook) -> bool;
|
||||
pub type RemoveSessionHookFunc = fn(session_id: String, key: &String) -> bool;
|
||||
|
||||
/// Hooks for session.
|
||||
#[derive(Clone)]
|
||||
pub enum SessionHook {
|
||||
OnSessionRgba(fn(String, Vec<i8>) -> Vec<i8>),
|
||||
}
|
||||
|
||||
// #[repr(C)]
|
||||
pub struct RustDeskApiTable {
|
||||
pub(crate) load_plugin: LoadPluginFunc,
|
||||
pub(crate) unload_plugin: UnloadPluginFunc,
|
||||
pub add_session: AddSessionFunc,
|
||||
pub remove_session: RemoveSessionFunc,
|
||||
pub add_session_hook: AddSessionHookFunc,
|
||||
pub remove_session_hook: RemoveSessionHookFunc,
|
||||
}
|
||||
|
||||
fn load_plugin(path: *const c_char) -> i32 {
|
||||
PLUGIN_REGISTRAR.load_plugin(path)
|
||||
}
|
||||
|
||||
fn unload_plugin(path: *const c_char) -> i32 {
|
||||
PLUGIN_REGISTRAR.unload_plugin(path)
|
||||
}
|
||||
|
||||
fn add_session(session_id: String) -> bool {
|
||||
// let mut sessions = SESSIONS.write().unwrap();
|
||||
// if sessions.contains_key(&session.id) {
|
||||
// return false;
|
||||
// }
|
||||
// let _ = sessions.insert(session.id.to_owned(), session);
|
||||
// true
|
||||
false
|
||||
}
|
||||
|
||||
fn remove_session(session_id: &String) -> bool {
|
||||
let mut sessions = SESSIONS.write().unwrap();
|
||||
if !sessions.contains_key(session_id) {
|
||||
return false;
|
||||
}
|
||||
let _ = sessions.remove(session_id);
|
||||
true
|
||||
}
|
||||
|
||||
fn add_session_hook(session_id: String, key: String, hook: SessionHook) -> bool {
|
||||
let sessions = SESSIONS.read().unwrap();
|
||||
if let Some(session) = sessions.get(&session_id) {
|
||||
return session.add_session_hook(key, hook);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn remove_session_hook(session_id: String, key: &String) -> bool {
|
||||
let sessions = SESSIONS.read().unwrap();
|
||||
if let Some(session) = sessions.get(&session_id) {
|
||||
return session.remove_session_hook(key);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
impl Default for RustDeskApiTable {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
load_plugin,
|
||||
unload_plugin,
|
||||
add_session,
|
||||
remove_session,
|
||||
add_session_hook,
|
||||
remove_session_hook,
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/cli.rs
34
src/cli.rs
@@ -48,18 +48,24 @@ impl Interface for Session {
|
||||
}
|
||||
|
||||
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str) {
|
||||
if msgtype == "input-password" {
|
||||
self.sender
|
||||
.send(Data::Login((self.password.clone(), true)))
|
||||
.ok();
|
||||
} else if msgtype == "re-input-password" {
|
||||
log::error!("{}: {}", title, text);
|
||||
let pass = rpassword::prompt_password("Enter password: ").unwrap();
|
||||
self.sender.send(Data::Login((pass, true))).ok();
|
||||
} else if msgtype.contains("error") {
|
||||
log::error!("{}: {}: {}", msgtype, title, text);
|
||||
} else {
|
||||
log::info!("{}: {}: {}", msgtype, title, text);
|
||||
match msgtype {
|
||||
"input-password" => {
|
||||
self.sender
|
||||
.send(Data::Login((self.password.clone(), true)))
|
||||
.ok();
|
||||
}
|
||||
"re-input-password" => {
|
||||
log::error!("{}: {}", title, text);
|
||||
let password = rpassword::prompt_password("Enter password: ").unwrap();
|
||||
let login_data = Data::Login((password, true));
|
||||
self.sender.send(login_data).ok();
|
||||
}
|
||||
msg if msg.contains("error") => {
|
||||
log::error!("{}: {}: {}", msgtype, title, text);
|
||||
}
|
||||
_ => {
|
||||
log::info!("{}: {}: {}", msgtype, title, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +85,8 @@ impl Interface for Session {
|
||||
handle_hash(self.lc.clone(), &pass, hash, self, peer).await;
|
||||
}
|
||||
|
||||
async fn handle_login_from_ui(&mut self, password: String, remember: bool, peer: &mut Stream) {
|
||||
handle_login_from_ui(self.lc.clone(), password, remember, peer).await;
|
||||
async fn handle_login_from_ui(&mut self, os_username: String, os_password: String, password: String, remember: bool, peer: &mut Stream) {
|
||||
handle_login_from_ui(self.lc.clone(), os_username, os_password, password, remember, peer).await;
|
||||
}
|
||||
|
||||
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {
|
||||
|
||||
502
src/client.rs
502
src/client.rs
@@ -1,9 +1,12 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::SocketAddr,
|
||||
ops::{Deref, Not},
|
||||
ops::Deref,
|
||||
str::FromStr,
|
||||
sync::{mpsc, Arc, Mutex, RwLock},
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc, Arc, Mutex, RwLock,
|
||||
},
|
||||
};
|
||||
|
||||
pub use async_trait::async_trait;
|
||||
@@ -13,11 +16,15 @@ use cpal::{
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
Device, Host, StreamConfig,
|
||||
};
|
||||
use crossbeam_queue::ArrayQueue;
|
||||
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
use ringbuf::{ring_buffer::RbBase, Rb};
|
||||
use sha2::{Digest, Sha256};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use file_trait::FileManager;
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::tokio::sync::mpsc::UnboundedSender;
|
||||
use hbb_common::{
|
||||
@@ -39,24 +46,20 @@ use hbb_common::{
|
||||
tokio::time::Duration,
|
||||
AddrMangle, ResultType, Stream,
|
||||
};
|
||||
pub use helper::LatencyController;
|
||||
pub use helper::*;
|
||||
use scrap::{
|
||||
codec::{Decoder, DecoderCfg},
|
||||
codec::Decoder,
|
||||
record::{Recorder, RecorderContext},
|
||||
ImageFormat, VpxDecoderConfig, VpxVideoCodecId,
|
||||
ImageFormat,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
common::{self, is_keyboard_mode_supported},
|
||||
server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED},
|
||||
};
|
||||
use crate::common::{self, is_keyboard_mode_supported};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::{
|
||||
common::{check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL},
|
||||
ui_session_interface::SessionPermissionConfig,
|
||||
};
|
||||
use crate::common::{check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL};
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::ui_session_interface::SessionPermissionConfig;
|
||||
|
||||
pub use super::lang::*;
|
||||
|
||||
@@ -66,6 +69,32 @@ pub mod io_loop;
|
||||
|
||||
pub const MILLI1: Duration = Duration::from_millis(1);
|
||||
pub const SEC30: Duration = Duration::from_secs(30);
|
||||
pub const VIDEO_QUEUE_SIZE: usize = 120;
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
pub const LOGIN_MSG_DESKTOP_NOT_INITED: &str = "Desktop env is not inited";
|
||||
pub const LOGIN_MSG_DESKTOP_SESSION_NOT_READY: &str = "Desktop session not ready";
|
||||
pub const LOGIN_MSG_DESKTOP_XSESSION_FAILED: &str = "Desktop xsession failed";
|
||||
pub const LOGIN_MSG_DESKTOP_SESSION_ANOTHER_USER: &str = "Desktop session another user login";
|
||||
pub const LOGIN_MSG_DESKTOP_XORG_NOT_FOUND: &str = "Desktop xorg not found";
|
||||
// ls /usr/share/xsessions/
|
||||
pub const LOGIN_MSG_DESKTOP_NO_DESKTOP: &str = "Desktop none";
|
||||
pub const LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_EMPTY: &str =
|
||||
"Desktop session not ready, password empty";
|
||||
pub const LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG: &str =
|
||||
"Desktop session not ready, password wrong";
|
||||
pub const LOGIN_MSG_PASSWORD_EMPTY: &str = "Empty Password";
|
||||
pub const LOGIN_MSG_PASSWORD_WRONG: &str = "Wrong Password";
|
||||
pub const LOGIN_MSG_NO_PASSWORD_ACCESS: &str = "No Password Access";
|
||||
pub const LOGIN_MSG_OFFLINE: &str = "Offline";
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version.";
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const SCRAP_OTHER_VERSION_OR_X11_REQUIRED: &str =
|
||||
"Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.";
|
||||
pub const SCRAP_X11_REQUIRED: &str = "x11 expected";
|
||||
pub const SCRAP_X11_REF_URL: &str = "https://rustdesk.com/docs/en/manual/linux/#x11-required";
|
||||
|
||||
/// Client of the remote desktop.
|
||||
pub struct Client;
|
||||
@@ -88,6 +117,12 @@ lazy_static::lazy_static! {
|
||||
static ref TEXT_CLIPBOARD_STATE: Arc<Mutex<TextClipboardState>> = Arc::new(Mutex::new(TextClipboardState::new()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn get_old_clipboard_text() -> &'static Arc<Mutex<String>> {
|
||||
&OLD_CLIPBOARD_TEXT
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn get_key_state(key: enigo::Key) -> bool {
|
||||
use enigo::KeyboardControllable;
|
||||
@@ -130,6 +165,7 @@ impl OboePlayer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn is_null(&self) -> bool {
|
||||
self.raw.is_null()
|
||||
}
|
||||
@@ -165,7 +201,7 @@ impl Client {
|
||||
token: &str,
|
||||
conn_type: ConnType,
|
||||
interface: impl Interface,
|
||||
) -> ResultType<(Stream, bool)> {
|
||||
) -> ResultType<(Stream, bool, Option<Vec<u8>>)> {
|
||||
match Self::_start(peer, key, token, conn_type, interface).await {
|
||||
Err(err) => {
|
||||
let err_str = err.to_string();
|
||||
@@ -186,7 +222,7 @@ impl Client {
|
||||
token: &str,
|
||||
conn_type: ConnType,
|
||||
interface: impl Interface,
|
||||
) -> ResultType<(Stream, bool)> {
|
||||
) -> ResultType<(Stream, bool, Option<Vec<u8>>)> {
|
||||
// to-do: remember the port for each peer, so that we can retry easier
|
||||
if hbb_common::is_ip_str(peer) {
|
||||
return Ok((
|
||||
@@ -196,6 +232,7 @@ impl Client {
|
||||
)
|
||||
.await?,
|
||||
true,
|
||||
None,
|
||||
));
|
||||
}
|
||||
// Allow connect to {domain}:{port}
|
||||
@@ -203,6 +240,7 @@ impl Client {
|
||||
return Ok((
|
||||
socket_client::connect_tcp(peer, RENDEZVOUS_TIMEOUT).await?,
|
||||
true,
|
||||
None,
|
||||
));
|
||||
}
|
||||
let (mut rendezvous_server, servers, contained) = crate::get_rendezvous_server(1_000).await;
|
||||
@@ -298,7 +336,7 @@ impl Client {
|
||||
my_addr.is_ipv4(),
|
||||
)
|
||||
.await?;
|
||||
Self::secure_connection(
|
||||
let pk = Self::secure_connection(
|
||||
peer,
|
||||
signed_id_pk,
|
||||
key,
|
||||
@@ -307,7 +345,7 @@ impl Client {
|
||||
interface,
|
||||
)
|
||||
.await?;
|
||||
return Ok((conn, false));
|
||||
return Ok((conn, false, pk));
|
||||
}
|
||||
_ => {
|
||||
log::error!("Unexpected protobuf msg received: {:?}", msg_in);
|
||||
@@ -368,7 +406,7 @@ impl Client {
|
||||
token: &str,
|
||||
conn_type: ConnType,
|
||||
interface: impl Interface,
|
||||
) -> ResultType<(Stream, bool)> {
|
||||
) -> ResultType<(Stream, bool, Option<Vec<u8>>)> {
|
||||
let direct_failures = PeerConfig::load(peer_id).direct_failures;
|
||||
let mut connect_timeout = 0;
|
||||
const MIN: u64 = 1000;
|
||||
@@ -438,8 +476,9 @@ impl Client {
|
||||
}
|
||||
let mut conn = conn?;
|
||||
log::info!("{:?} used to establish connection", start.elapsed());
|
||||
Self::secure_connection(peer_id, signed_id_pk, key, &mut conn, direct, interface).await?;
|
||||
Ok((conn, direct))
|
||||
let pk = Self::secure_connection(peer_id, signed_id_pk, key, &mut conn, direct, interface)
|
||||
.await?;
|
||||
Ok((conn, direct, pk))
|
||||
}
|
||||
|
||||
/// Establish secure connection with the server.
|
||||
@@ -450,17 +489,19 @@ impl Client {
|
||||
conn: &mut Stream,
|
||||
direct: bool,
|
||||
interface: impl Interface,
|
||||
) -> ResultType<()> {
|
||||
) -> ResultType<Option<Vec<u8>>> {
|
||||
let rs_pk = get_rs_pk(if key.is_empty() {
|
||||
hbb_common::config::RS_PUB_KEY
|
||||
} else {
|
||||
key
|
||||
});
|
||||
let mut sign_pk = None;
|
||||
let mut option_pk = None;
|
||||
if !signed_id_pk.is_empty() && rs_pk.is_some() {
|
||||
if let Ok((id, pk)) = decode_id_pk(&signed_id_pk, &rs_pk.unwrap()) {
|
||||
if id == peer_id {
|
||||
sign_pk = Some(sign::PublicKey(pk));
|
||||
option_pk = Some(pk.to_vec());
|
||||
}
|
||||
}
|
||||
if sign_pk.is_none() {
|
||||
@@ -472,7 +513,7 @@ impl Client {
|
||||
None => {
|
||||
// send an empty message out in case server is setting up secure and waiting for first message
|
||||
conn.send(&Message::new()).await?;
|
||||
return Ok(());
|
||||
return Ok(option_pk);
|
||||
}
|
||||
};
|
||||
match timeout(READ_TIMEOUT, conn.next()).await? {
|
||||
@@ -525,7 +566,7 @@ impl Client {
|
||||
bail!("Reset by the peer");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Ok(option_pk)
|
||||
}
|
||||
|
||||
/// Request a relay connection to the server.
|
||||
@@ -628,8 +669,13 @@ impl Client {
|
||||
TEXT_CLIPBOARD_STATE.lock().unwrap().running = false;
|
||||
}
|
||||
|
||||
// `try_start_clipboard` is called by all session when connection is established. (When handling peer info).
|
||||
// This function only create one thread with a loop, the loop is shared by all sessions.
|
||||
// After all sessions are end, the loop exists.
|
||||
//
|
||||
// If clipboard update is detected, the text will be sent to all sessions by `send_text_clipboard_msg`.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn try_start_clipboard(_conf_tx: Option<(SessionPermissionConfig, UnboundedSender<Data>)>) {
|
||||
fn try_start_clipboard(_ctx: Option<ClientClipboardContext>) {
|
||||
let mut clipboard_lock = TEXT_CLIPBOARD_STATE.lock().unwrap();
|
||||
if clipboard_lock.running {
|
||||
return;
|
||||
@@ -656,9 +702,9 @@ impl Client {
|
||||
#[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));
|
||||
if let Some(ctx) = &_ctx {
|
||||
if ctx.cfg.is_text_clipboard_required() {
|
||||
let _ = ctx.tx.send(Data::Message(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -672,6 +718,7 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn get_current_text_clipboard_msg() -> Option<Message> {
|
||||
let txt = &*OLD_CLIPBOARD_TEXT.lock().unwrap();
|
||||
@@ -702,24 +749,28 @@ pub struct AudioHandler {
|
||||
#[cfg(target_os = "linux")]
|
||||
simple: Option<psimple::Simple>,
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
audio_buffer: Arc<std::sync::Mutex<std::collections::vec_deque::VecDeque<f32>>>,
|
||||
audio_buffer: AudioBuffer,
|
||||
sample_rate: (u32, u32),
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
audio_stream: Option<Box<dyn StreamTrait>>,
|
||||
channels: u16,
|
||||
latency_controller: Arc<Mutex<LatencyController>>,
|
||||
ignore_count: i32,
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
ready: Arc<std::sync::Mutex<bool>>,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
struct AudioBuffer(pub Arc<std::sync::Mutex<ringbuf::HeapRb<f32>>>);
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
impl Default for AudioBuffer {
|
||||
fn default() -> Self {
|
||||
Self(Arc::new(std::sync::Mutex::new(
|
||||
ringbuf::HeapRb::<f32>::new(48000 * 2), // 48000hz, 2 channel, 1 second
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioHandler {
|
||||
/// Create a new audio handler.
|
||||
pub fn new(latency_controller: Arc<Mutex<LatencyController>>) -> Self {
|
||||
AudioHandler {
|
||||
latency_controller,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the audio playback.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn start_audio(&mut self, format0: AudioFormat) -> ResultType<()> {
|
||||
@@ -778,9 +829,17 @@ impl AudioHandler {
|
||||
let mut config: StreamConfig = config.into();
|
||||
config.channels = format0.channels as _;
|
||||
match sample_format {
|
||||
cpal::SampleFormat::F32 => self.build_output_stream::<f32>(&config, &device)?,
|
||||
cpal::SampleFormat::I8 => self.build_output_stream::<i8>(&config, &device)?,
|
||||
cpal::SampleFormat::I16 => self.build_output_stream::<i16>(&config, &device)?,
|
||||
cpal::SampleFormat::I32 => self.build_output_stream::<i32>(&config, &device)?,
|
||||
cpal::SampleFormat::I64 => self.build_output_stream::<i64>(&config, &device)?,
|
||||
cpal::SampleFormat::U8 => self.build_output_stream::<u8>(&config, &device)?,
|
||||
cpal::SampleFormat::U16 => self.build_output_stream::<u16>(&config, &device)?,
|
||||
cpal::SampleFormat::U32 => self.build_output_stream::<u32>(&config, &device)?,
|
||||
cpal::SampleFormat::U64 => self.build_output_stream::<u64>(&config, &device)?,
|
||||
cpal::SampleFormat::F32 => self.build_output_stream::<f32>(&config, &device)?,
|
||||
cpal::SampleFormat::F64 => self.build_output_stream::<f64>(&config, &device)?,
|
||||
f => bail!("unsupported audio format: {:?}", f),
|
||||
}
|
||||
self.sample_rate = (format0.sample_rate, config.sample_rate.0);
|
||||
Ok(())
|
||||
@@ -802,26 +861,10 @@ impl AudioHandler {
|
||||
}
|
||||
|
||||
/// Handle audio frame and play it.
|
||||
#[inline]
|
||||
pub fn handle_frame(&mut self, frame: AudioFrame) {
|
||||
if frame.timestamp != 0 {
|
||||
if self
|
||||
.latency_controller
|
||||
.lock()
|
||||
.unwrap()
|
||||
.check_audio(frame.timestamp)
|
||||
.not()
|
||||
{
|
||||
self.ignore_count += 1;
|
||||
if self.ignore_count == 100 {
|
||||
self.ignore_count = 0;
|
||||
log::debug!("100 audio frames are ignored");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
if self.audio_stream.is_none() {
|
||||
if self.audio_stream.is_none() || !self.ready.lock().unwrap().clone() {
|
||||
return;
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -841,11 +884,7 @@ impl AudioHandler {
|
||||
{
|
||||
let sample_rate0 = self.sample_rate.0;
|
||||
let sample_rate = self.sample_rate.1;
|
||||
let audio_buffer = self.audio_buffer.clone();
|
||||
// avoiding memory overflow if audio_buffer consumer side has problem
|
||||
if audio_buffer.lock().unwrap().len() as u32 > sample_rate * 120 {
|
||||
*audio_buffer.lock().unwrap() = Default::default();
|
||||
}
|
||||
let audio_buffer = self.audio_buffer.0.clone();
|
||||
if sample_rate != sample_rate0 {
|
||||
let buffer = crate::resample_channels(
|
||||
&buffer[0..n],
|
||||
@@ -853,12 +892,12 @@ impl AudioHandler {
|
||||
sample_rate,
|
||||
channels,
|
||||
);
|
||||
audio_buffer.lock().unwrap().extend(buffer);
|
||||
audio_buffer.lock().unwrap().push_slice_overwrite(&buffer);
|
||||
} else {
|
||||
audio_buffer
|
||||
.lock()
|
||||
.unwrap()
|
||||
.extend(buffer[0..n].iter().cloned());
|
||||
.push_slice_overwrite(&buffer[0..n]);
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
@@ -877,7 +916,7 @@ impl AudioHandler {
|
||||
|
||||
/// Build audio output stream for current device.
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
fn build_output_stream<T: cpal::Sample>(
|
||||
fn build_output_stream<T: cpal::Sample + cpal::SizedSample + cpal::FromSample<f32>>(
|
||||
&mut self,
|
||||
config: &StreamConfig,
|
||||
device: &Device,
|
||||
@@ -886,24 +925,33 @@ impl AudioHandler {
|
||||
// too many errors, will improve later
|
||||
log::trace!("an error occurred on stream: {}", err);
|
||||
};
|
||||
let audio_buffer = self.audio_buffer.clone();
|
||||
let audio_buffer = self.audio_buffer.0.clone();
|
||||
let ready = self.ready.clone();
|
||||
let timeout = None;
|
||||
let stream = device.build_output_stream(
|
||||
config,
|
||||
move |data: &mut [T], _: &_| {
|
||||
if !*ready.lock().unwrap() {
|
||||
*ready.lock().unwrap() = true;
|
||||
}
|
||||
let mut lock = audio_buffer.lock().unwrap();
|
||||
let mut n = data.len();
|
||||
if lock.len() < n {
|
||||
n = lock.len();
|
||||
if lock.occupied_len() < n {
|
||||
n = lock.occupied_len();
|
||||
}
|
||||
let mut input = lock.drain(0..n);
|
||||
let mut elems = vec![0.0f32; n];
|
||||
lock.pop_slice(&mut elems);
|
||||
drop(lock);
|
||||
let mut input = elems.into_iter();
|
||||
for sample in data.iter_mut() {
|
||||
*sample = match input.next() {
|
||||
Some(x) => T::from(&x),
|
||||
_ => T::from(&0.),
|
||||
Some(x) => T::from_sample(x),
|
||||
_ => T::from_sample(0.),
|
||||
};
|
||||
}
|
||||
},
|
||||
err_fn,
|
||||
timeout,
|
||||
)?;
|
||||
stream.play()?;
|
||||
self.audio_stream = Some(Box::new(stream));
|
||||
@@ -914,7 +962,6 @@ impl AudioHandler {
|
||||
/// Video handler for the [`Client`].
|
||||
pub struct VideoHandler {
|
||||
decoder: Decoder,
|
||||
latency_controller: Arc<Mutex<LatencyController>>,
|
||||
pub rgb: Vec<u8>,
|
||||
recorder: Arc<Mutex<Option<Recorder>>>,
|
||||
record: bool,
|
||||
@@ -922,15 +969,9 @@ pub struct VideoHandler {
|
||||
|
||||
impl VideoHandler {
|
||||
/// Create a new video handler.
|
||||
pub fn new(latency_controller: Arc<Mutex<LatencyController>>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
VideoHandler {
|
||||
decoder: Decoder::new(DecoderCfg {
|
||||
vpx: VpxDecoderConfig {
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
},
|
||||
}),
|
||||
latency_controller,
|
||||
decoder: Decoder::new(),
|
||||
rgb: Default::default(),
|
||||
recorder: Default::default(),
|
||||
record: false,
|
||||
@@ -938,14 +979,8 @@ impl VideoHandler {
|
||||
}
|
||||
|
||||
/// Handle a new video frame.
|
||||
#[inline]
|
||||
pub fn handle_frame(&mut self, vf: VideoFrame) -> ResultType<bool> {
|
||||
if vf.timestamp != 0 {
|
||||
// Update the latency controller with the latest timestamp.
|
||||
self.latency_controller
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update_video(vf.timestamp);
|
||||
}
|
||||
match &vf.union {
|
||||
Some(frame) => {
|
||||
let res = self.decoder.handle_video_frame(
|
||||
@@ -968,12 +1003,7 @@ impl VideoHandler {
|
||||
|
||||
/// Reset the decoder.
|
||||
pub fn reset(&mut self) {
|
||||
self.decoder = Decoder::new(DecoderCfg {
|
||||
vpx: VpxDecoderConfig {
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: 1,
|
||||
},
|
||||
});
|
||||
self.decoder = Decoder::new();
|
||||
}
|
||||
|
||||
/// Start or stop screen record.
|
||||
@@ -987,13 +1017,14 @@ impl VideoHandler {
|
||||
filename: "".to_owned(),
|
||||
width: w as _,
|
||||
height: h as _,
|
||||
codec_id: scrap::record::RecordCodecID::VP9,
|
||||
format: scrap::CodecFormat::VP9,
|
||||
tx: None,
|
||||
})
|
||||
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))));
|
||||
} else {
|
||||
self.recorder = Default::default();
|
||||
}
|
||||
|
||||
self.record = start;
|
||||
}
|
||||
}
|
||||
@@ -1012,7 +1043,7 @@ pub struct LoginConfigHandler {
|
||||
pub conn_id: i32,
|
||||
features: Option<Features>,
|
||||
session_id: u64,
|
||||
pub supported_encoding: Option<(bool, bool)>,
|
||||
pub supported_encoding: SupportedEncoding,
|
||||
pub restarting_remote_device: bool,
|
||||
pub force_relay: bool,
|
||||
pub direct: Option<bool>,
|
||||
@@ -1060,7 +1091,7 @@ impl LoginConfigHandler {
|
||||
self.remember = !config.password.is_empty();
|
||||
self.config = config;
|
||||
self.session_id = rand::random();
|
||||
self.supported_encoding = None;
|
||||
self.supported_encoding = Default::default();
|
||||
self.restarting_remote_device = false;
|
||||
self.force_relay = !self.get_option("force-always-relay").is_empty() || force_relay;
|
||||
self.direct = None;
|
||||
@@ -1314,11 +1345,12 @@ impl LoginConfigHandler {
|
||||
config.custom_image_quality[0]
|
||||
};
|
||||
msg.custom_image_quality = quality << 8;
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(custom_fps) = self.options.get("custom-fps") {
|
||||
msg.custom_fps = custom_fps.parse().unwrap_or(30);
|
||||
}
|
||||
n += 1;
|
||||
}
|
||||
if let Some(custom_fps) = self.options.get("custom-fps") {
|
||||
msg.custom_fps = custom_fps.parse().unwrap_or(30);
|
||||
}
|
||||
let view_only = self.get_toggle_option("view-only");
|
||||
if view_only {
|
||||
msg.disable_keyboard = BoolOption::Yes.into();
|
||||
@@ -1344,8 +1376,8 @@ impl LoginConfigHandler {
|
||||
msg.disable_clipboard = BoolOption::Yes.into();
|
||||
n += 1;
|
||||
}
|
||||
let state = Decoder::video_codec_state(&self.id);
|
||||
msg.video_codec_state = hbb_common::protobuf::MessageField::some(state);
|
||||
msg.supported_decoding =
|
||||
hbb_common::protobuf::MessageField::some(Decoder::supported_decodings(Some(&self.id)));
|
||||
n += 1;
|
||||
|
||||
if n > 0 {
|
||||
@@ -1578,10 +1610,7 @@ impl LoginConfigHandler {
|
||||
self.conn_id = pi.conn_id;
|
||||
// no matter if change, for update file time
|
||||
self.save_config(config);
|
||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
||||
{
|
||||
self.supported_encoding = Some((pi.encoding.h264, pi.encoding.h265));
|
||||
}
|
||||
self.supported_encoding = pi.encoding.clone().unwrap_or_default();
|
||||
}
|
||||
|
||||
pub fn get_remote_dir(&self) -> String {
|
||||
@@ -1604,7 +1633,12 @@ impl LoginConfigHandler {
|
||||
}
|
||||
|
||||
/// Create a [`Message`] for login.
|
||||
fn create_login_msg(&self, password: Vec<u8>) -> Message {
|
||||
fn create_login_msg(
|
||||
&self,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: Vec<u8>,
|
||||
) -> Message {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let my_id = Config::get_id_or(crate::common::DEVICE_ID.lock().unwrap().clone());
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -1617,6 +1651,12 @@ impl LoginConfigHandler {
|
||||
option: self.get_option_message(true).into(),
|
||||
session_id: self.session_id,
|
||||
version: crate::VERSION.to_string(),
|
||||
os_login: Some(OSLogin {
|
||||
username: os_username,
|
||||
password: os_password,
|
||||
..Default::default()
|
||||
})
|
||||
.into(),
|
||||
..Default::default()
|
||||
};
|
||||
match self.conn_type {
|
||||
@@ -1639,10 +1679,10 @@ impl LoginConfigHandler {
|
||||
}
|
||||
|
||||
pub fn change_prefer_codec(&self) -> Message {
|
||||
let state = scrap::codec::Decoder::video_codec_state(&self.id);
|
||||
let decoding = scrap::codec::Decoder::supported_decodings(Some(&self.id));
|
||||
let mut misc = Misc::new();
|
||||
misc.set_option(OptionMessage {
|
||||
video_codec_state: hbb_common::protobuf::MessageField::some(state),
|
||||
supported_decoding: hbb_common::protobuf::MessageField::some(decoding),
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
@@ -1674,8 +1714,9 @@ impl LoginConfigHandler {
|
||||
|
||||
/// Media data.
|
||||
pub enum MediaData {
|
||||
VideoFrame(VideoFrame),
|
||||
AudioFrame(AudioFrame),
|
||||
VideoQueue,
|
||||
VideoFrame(Box<VideoFrame>),
|
||||
AudioFrame(Box<AudioFrame>),
|
||||
AudioFormat(AudioFormat),
|
||||
Reset,
|
||||
RecordScreen(bool, i32, i32, String),
|
||||
@@ -1689,24 +1730,64 @@ pub type MediaSender = mpsc::Sender<MediaData>;
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `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)
|
||||
pub fn start_video_audio_threads<F>(
|
||||
video_callback: F,
|
||||
) -> (
|
||||
MediaSender,
|
||||
MediaSender,
|
||||
Arc<ArrayQueue<VideoFrame>>,
|
||||
Arc<AtomicUsize>,
|
||||
)
|
||||
where
|
||||
F: 'static + FnMut(&mut Vec<u8>) + Send,
|
||||
{
|
||||
let (video_sender, video_receiver) = mpsc::channel::<MediaData>();
|
||||
let video_queue = Arc::new(ArrayQueue::<VideoFrame>::new(VIDEO_QUEUE_SIZE));
|
||||
let video_queue_cloned = video_queue.clone();
|
||||
let mut video_callback = video_callback;
|
||||
|
||||
let latency_controller = LatencyController::new();
|
||||
let latency_controller_cl = latency_controller.clone();
|
||||
let mut duration = std::time::Duration::ZERO;
|
||||
let mut count = 0;
|
||||
let fps = Arc::new(AtomicUsize::new(0));
|
||||
let decode_fps = fps.clone();
|
||||
let mut skip_beginning = 0;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut video_handler = VideoHandler::new(latency_controller);
|
||||
let mut video_handler = VideoHandler::new();
|
||||
loop {
|
||||
if let Ok(data) = video_receiver.recv() {
|
||||
match data {
|
||||
MediaData::VideoFrame(vf) => {
|
||||
MediaData::VideoFrame(_) | MediaData::VideoQueue => {
|
||||
let vf = if let MediaData::VideoFrame(vf) = data {
|
||||
*vf
|
||||
} else {
|
||||
if let Some(vf) = video_queue.pop() {
|
||||
vf
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let start = std::time::Instant::now();
|
||||
if let Ok(true) = video_handler.handle_frame(vf) {
|
||||
video_callback(&mut video_handler.rgb);
|
||||
// fps calculation
|
||||
// The first frame will be very slow
|
||||
if skip_beginning < 5 {
|
||||
skip_beginning += 1;
|
||||
continue;
|
||||
}
|
||||
duration += start.elapsed();
|
||||
count += 1;
|
||||
if count % 10 == 0 {
|
||||
fps.store(
|
||||
(count * 1000 / duration.as_millis()) as usize,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
}
|
||||
// Clear to get real-time fps
|
||||
if count > 300 {
|
||||
count = 0;
|
||||
duration = Duration::ZERO;
|
||||
}
|
||||
}
|
||||
}
|
||||
MediaData::Reset => {
|
||||
@@ -1723,24 +1804,21 @@ where
|
||||
}
|
||||
log::info!("Video decoder loop exits");
|
||||
});
|
||||
let audio_sender = start_audio_thread(Some(latency_controller_cl));
|
||||
return (video_sender, audio_sender);
|
||||
let audio_sender = start_audio_thread();
|
||||
return (video_sender, audio_sender, video_queue_cloned, decode_fps);
|
||||
}
|
||||
|
||||
/// 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());
|
||||
pub fn start_audio_thread() -> MediaSender {
|
||||
let (audio_sender, audio_receiver) = mpsc::channel::<MediaData>();
|
||||
std::thread::spawn(move || {
|
||||
let mut audio_handler = AudioHandler::new(latency_controller);
|
||||
let mut audio_handler = AudioHandler::default();
|
||||
loop {
|
||||
if let Ok(data) = audio_receiver.recv() {
|
||||
match data {
|
||||
MediaData::AudioFrame(af) => {
|
||||
audio_handler.handle_frame(af);
|
||||
audio_handler.handle_frame(*af);
|
||||
}
|
||||
MediaData::AudioFormat(f) => {
|
||||
log::debug!("recved audio format, sample rate={}", f.sample_rate);
|
||||
@@ -1908,6 +1986,71 @@ fn _input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
interface.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct LoginErrorMsgBox {
|
||||
msgtype: &'static str,
|
||||
title: &'static str,
|
||||
text: &'static str,
|
||||
link: &'static str,
|
||||
try_again: bool,
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref LOGIN_ERROR_MAP: Arc<HashMap<&'static str, LoginErrorMsgBox>> = {
|
||||
use hbb_common::config::LINK_HEADLESS_LINUX_SUPPORT;
|
||||
let map = HashMap::from([(LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LoginErrorMsgBox{
|
||||
msgtype: "session-login",
|
||||
title: "",
|
||||
text: "",
|
||||
link: "",
|
||||
try_again: true,
|
||||
}), (LOGIN_MSG_DESKTOP_XSESSION_FAILED, LoginErrorMsgBox{
|
||||
msgtype: "session-re-login",
|
||||
title: "",
|
||||
text: "",
|
||||
link: "",
|
||||
try_again: true,
|
||||
}), (LOGIN_MSG_DESKTOP_SESSION_ANOTHER_USER, LoginErrorMsgBox{
|
||||
msgtype: "info-nocancel",
|
||||
title: "another_user_login_title_tip",
|
||||
text: "another_user_login_text_tip",
|
||||
link: "",
|
||||
try_again: false,
|
||||
}), (LOGIN_MSG_DESKTOP_XORG_NOT_FOUND, LoginErrorMsgBox{
|
||||
msgtype: "info-nocancel",
|
||||
title: "xorg_not_found_title_tip",
|
||||
text: "xorg_not_found_text_tip",
|
||||
link: LINK_HEADLESS_LINUX_SUPPORT,
|
||||
try_again: true,
|
||||
}), (LOGIN_MSG_DESKTOP_NO_DESKTOP, LoginErrorMsgBox{
|
||||
msgtype: "info-nocancel",
|
||||
title: "no_desktop_title_tip",
|
||||
text: "no_desktop_text_tip",
|
||||
link: LINK_HEADLESS_LINUX_SUPPORT,
|
||||
try_again: true,
|
||||
}), (LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_EMPTY, LoginErrorMsgBox{
|
||||
msgtype: "session-login-password",
|
||||
title: "",
|
||||
text: "",
|
||||
link: "",
|
||||
try_again: true,
|
||||
}), (LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG, LoginErrorMsgBox{
|
||||
msgtype: "session-login-re-password",
|
||||
title: "",
|
||||
text: "",
|
||||
link: "",
|
||||
try_again: true,
|
||||
}), (LOGIN_MSG_NO_PASSWORD_ACCESS, LoginErrorMsgBox{
|
||||
msgtype: "wait-remote-accept-nook",
|
||||
title: "Prompt",
|
||||
text: "Please wait for the remote side to accept your session request...",
|
||||
link: "",
|
||||
try_again: true,
|
||||
})]);
|
||||
Arc::new(map)
|
||||
};
|
||||
}
|
||||
|
||||
/// Handle login error.
|
||||
/// Return true if the password is wrong, return false if there's an actual error.
|
||||
pub fn handle_login_error(
|
||||
@@ -1915,19 +2058,27 @@ pub fn handle_login_error(
|
||||
err: &str,
|
||||
interface: &impl Interface,
|
||||
) -> bool {
|
||||
if err == "Wrong Password" {
|
||||
if err == LOGIN_MSG_PASSWORD_EMPTY {
|
||||
lc.write().unwrap().password = Default::default();
|
||||
interface.msgbox("input-password", "Password Required", "", "");
|
||||
true
|
||||
} else if err == LOGIN_MSG_PASSWORD_WRONG {
|
||||
lc.write().unwrap().password = Default::default();
|
||||
interface.msgbox("re-input-password", err, "Do you want to enter again?", "");
|
||||
true
|
||||
} else if err == "No Password Access" {
|
||||
lc.write().unwrap().password = Default::default();
|
||||
interface.msgbox(
|
||||
"wait-remote-accept-nook",
|
||||
"Prompt",
|
||||
"Please wait for the remote side to accept your session request...",
|
||||
"",
|
||||
);
|
||||
true
|
||||
} else if LOGIN_ERROR_MAP.contains_key(err) {
|
||||
if let Some(msgbox_info) = LOGIN_ERROR_MAP.get(err) {
|
||||
interface.msgbox(
|
||||
msgbox_info.msgtype,
|
||||
msgbox_info.title,
|
||||
msgbox_info.text,
|
||||
msgbox_info.link,
|
||||
);
|
||||
msgbox_info.try_again
|
||||
} else {
|
||||
// unreachable!
|
||||
false
|
||||
}
|
||||
} else {
|
||||
if err.contains(SCRAP_X11_REQUIRED) {
|
||||
interface.msgbox("error", "Login Error", err, SCRAP_X11_REF_URL);
|
||||
@@ -1975,16 +2126,21 @@ pub async fn handle_hash(
|
||||
if password.is_empty() {
|
||||
password = lc.read().unwrap().config.password.clone();
|
||||
}
|
||||
if password.is_empty() {
|
||||
let password = if password.is_empty() {
|
||||
// login without password, the remote side can click accept
|
||||
send_login(lc.clone(), Vec::new(), peer).await;
|
||||
interface.msgbox("input-password", "Password Required", "", "");
|
||||
Vec::new()
|
||||
} else {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&password);
|
||||
hasher.update(&hash.challenge);
|
||||
send_login(lc.clone(), hasher.finalize()[..].into(), peer).await;
|
||||
}
|
||||
hasher.finalize()[..].into()
|
||||
};
|
||||
|
||||
let os_username = lc.read().unwrap().get_option("os-username");
|
||||
let os_password = lc.read().unwrap().get_option("os-password");
|
||||
|
||||
send_login(lc.clone(), os_username, os_password, password, peer).await;
|
||||
lc.write().unwrap().hash = hash;
|
||||
}
|
||||
|
||||
@@ -1993,10 +2149,21 @@ pub async fn handle_hash(
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `lc` - Login config.
|
||||
/// * `os_username` - OS username.
|
||||
/// * `os_password` - OS password.
|
||||
/// * `password` - Password.
|
||||
/// * `peer` - [`Stream`] for communicating with peer.
|
||||
async fn send_login(lc: Arc<RwLock<LoginConfigHandler>>, password: Vec<u8>, peer: &mut Stream) {
|
||||
let msg_out = lc.read().unwrap().create_login_msg(password);
|
||||
async fn send_login(
|
||||
lc: Arc<RwLock<LoginConfigHandler>>,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: Vec<u8>,
|
||||
peer: &mut Stream,
|
||||
) {
|
||||
let msg_out = lc
|
||||
.read()
|
||||
.unwrap()
|
||||
.create_login_msg(os_username, os_password, password);
|
||||
allow_err!(peer.send(&msg_out).await);
|
||||
}
|
||||
|
||||
@@ -2005,25 +2172,40 @@ async fn send_login(lc: Arc<RwLock<LoginConfigHandler>>, password: Vec<u8>, peer
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `lc` - Login config.
|
||||
/// * `os_username` - OS username.
|
||||
/// * `os_password` - OS password.
|
||||
/// * `password` - Password.
|
||||
/// * `remember` - Whether to remember password.
|
||||
/// * `peer` - [`Stream`] for communicating with peer.
|
||||
pub async fn handle_login_from_ui(
|
||||
lc: Arc<RwLock<LoginConfigHandler>>,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: String,
|
||||
remember: bool,
|
||||
peer: &mut Stream,
|
||||
) {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(password);
|
||||
hasher.update(&lc.read().unwrap().hash.salt);
|
||||
let res = hasher.finalize();
|
||||
lc.write().unwrap().remember = remember;
|
||||
lc.write().unwrap().password = res[..].into();
|
||||
let mut hash_password = if password.is_empty() {
|
||||
let mut password2 = lc.read().unwrap().password.clone();
|
||||
if password2.is_empty() {
|
||||
password2 = lc.read().unwrap().config.password.clone();
|
||||
}
|
||||
password2
|
||||
} else {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(password);
|
||||
hasher.update(&lc.read().unwrap().hash.salt);
|
||||
let res = hasher.finalize();
|
||||
lc.write().unwrap().remember = remember;
|
||||
lc.write().unwrap().password = res[..].into();
|
||||
res[..].into()
|
||||
};
|
||||
let mut hasher2 = Sha256::new();
|
||||
hasher2.update(&res[..]);
|
||||
hasher2.update(&hash_password[..]);
|
||||
hasher2.update(&lc.read().unwrap().hash.challenge);
|
||||
send_login(lc.clone(), hasher2.finalize()[..].into(), peer).await;
|
||||
hash_password = hasher2.finalize()[..].to_vec();
|
||||
|
||||
send_login(lc.clone(), os_username, os_password, hash_password, peer).await;
|
||||
}
|
||||
|
||||
async fn send_switch_login_request(
|
||||
@@ -2037,7 +2219,7 @@ async fn send_switch_login_request(
|
||||
lr: hbb_common::protobuf::MessageField::some(
|
||||
lc.read()
|
||||
.unwrap()
|
||||
.create_login_msg(vec![])
|
||||
.create_login_msg("".to_owned(), "".to_owned(), vec![])
|
||||
.login_request()
|
||||
.to_owned(),
|
||||
),
|
||||
@@ -2058,7 +2240,14 @@ pub trait Interface: Send + Clone + 'static + Sized {
|
||||
self.msgbox("error", "Error", err, "");
|
||||
}
|
||||
async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream);
|
||||
async fn handle_login_from_ui(&mut self, password: String, remember: bool, peer: &mut Stream);
|
||||
async fn handle_login_from_ui(
|
||||
&mut self,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: String,
|
||||
remember: bool,
|
||||
peer: &mut Stream,
|
||||
);
|
||||
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream);
|
||||
|
||||
fn get_login_config_handler(&self) -> Arc<RwLock<LoginConfigHandler>>;
|
||||
@@ -2078,7 +2267,7 @@ pub trait Interface: Send + Clone + 'static + Sized {
|
||||
#[derive(Clone)]
|
||||
pub enum Data {
|
||||
Close,
|
||||
Login((String, bool)),
|
||||
Login((String, String, String, bool)),
|
||||
Message(Message),
|
||||
SendFiles((i32, String, String, i32, bool, bool)),
|
||||
RemoveDirAll((i32, String, bool, bool)),
|
||||
@@ -2285,3 +2474,14 @@ fn decode_id_pk(signed: &[u8], key: &sign::PublicKey) -> ResultType<(String, [u8
|
||||
bail!("Wrong public length");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(crate) struct ClientClipboardContext;
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(crate) struct ClientClipboardContext {
|
||||
pub cfg: SessionPermissionConfig,
|
||||
pub tx: UnboundedSender<Data>,
|
||||
}
|
||||
|
||||
@@ -1,111 +1,8 @@
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use hbb_common::{
|
||||
log,
|
||||
message_proto::{video_frame, VideoFrame, Message, VoiceCallRequest, VoiceCallResponse}, get_time,
|
||||
get_time,
|
||||
message_proto::{Message, VoiceCallRequest, VoiceCallResponse},
|
||||
};
|
||||
|
||||
const MAX_LATENCY: i64 = 500;
|
||||
const MIN_LATENCY: i64 = 100;
|
||||
|
||||
/// Latency controller for syncing audio with the video stream.
|
||||
/// Only sync the audio to video, not the other way around.
|
||||
#[derive(Debug)]
|
||||
pub struct LatencyController {
|
||||
last_video_remote_ts: i64, // generated on remote device
|
||||
update_time: Instant,
|
||||
allow_audio: bool,
|
||||
audio_only: bool
|
||||
}
|
||||
|
||||
impl Default for LatencyController {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
last_video_remote_ts: Default::default(),
|
||||
update_time: Instant::now(),
|
||||
allow_audio: Default::default(),
|
||||
audio_only: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LatencyController {
|
||||
/// Create a new latency controller.
|
||||
pub fn new() -> Arc<Mutex<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;
|
||||
self.update_time = Instant::now();
|
||||
}
|
||||
|
||||
/// Check if the audio should be played based on the current latency.
|
||||
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 = 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 {
|
||||
log::debug!("LATENCY > {}ms cut off, latency:{}", MAX_LATENCY, latency);
|
||||
self.allow_audio = false;
|
||||
}
|
||||
} else {
|
||||
if latency.abs() < MIN_LATENCY {
|
||||
log::debug!("LATENCY < {}ms resume, latency:{}", MIN_LATENCY, latency);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum CodecFormat {
|
||||
VP9,
|
||||
H264,
|
||||
H265,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<&VideoFrame> for CodecFormat {
|
||||
fn from(it: &VideoFrame) -> Self {
|
||||
match it.union {
|
||||
Some(video_frame::Union::Vp9s(_)) => CodecFormat::VP9,
|
||||
Some(video_frame::Union::H264s(_)) => CodecFormat::H264,
|
||||
Some(video_frame::Union::H265s(_)) => CodecFormat::H265,
|
||||
_ => CodecFormat::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for CodecFormat {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
CodecFormat::VP9 => "VP9".into(),
|
||||
CodecFormat::H264 => "H264".into(),
|
||||
CodecFormat::H265 => "H265".into(),
|
||||
CodecFormat::Unknown => "Unknow".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
use scrap::CodecFormat;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct QualityStatus {
|
||||
@@ -135,4 +32,4 @@ pub fn new_voice_call_response(request_timestamp: i64, accepted: bool) -> Messag
|
||||
let mut msg = Message::new();
|
||||
msg.set_voice_call_response(resp);
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
use std::num::NonZeroI64;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use clipboard::{cliprdr::CliprdrClientContext, ContextSend};
|
||||
use crossbeam_queue::ArrayQueue;
|
||||
use hbb_common::config::{PeerConfig, TransferSerde};
|
||||
use hbb_common::fs::{
|
||||
can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult,
|
||||
@@ -13,6 +16,8 @@ use hbb_common::fs::{
|
||||
use hbb_common::message_proto::permission_info::Permission;
|
||||
use hbb_common::protobuf::Message as _;
|
||||
use hbb_common::rendezvous_proto::ConnType;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::sleep;
|
||||
use hbb_common::tokio::sync::mpsc::error::TryRecvError;
|
||||
#[cfg(windows)]
|
||||
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
||||
@@ -21,22 +26,23 @@ use hbb_common::tokio::{
|
||||
sync::mpsc,
|
||||
time::{self, Duration, Instant, Interval},
|
||||
};
|
||||
use hbb_common::{allow_err, get_time, message_proto::*, sleep};
|
||||
use hbb_common::{fs, log, Stream};
|
||||
use hbb_common::{allow_err, fs, get_time, log, message_proto::*, Stream};
|
||||
use scrap::CodecFormat;
|
||||
|
||||
use crate::client::{
|
||||
new_voice_call_request, Client, CodecFormat, MediaData, MediaSender, QualityStatus, MILLI1,
|
||||
SEC30,
|
||||
new_voice_call_request, Client, MediaData, MediaSender, QualityStatus, MILLI1, SEC30,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::common::update_clipboard;
|
||||
use crate::common::{self, 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};
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
use crate::{audio_service, ConnInner, CLIENT_SERVER};
|
||||
use crate::{client::Data, client::Interface};
|
||||
|
||||
pub struct Remote<T: InvokeUiSession> {
|
||||
handler: Session<T>,
|
||||
video_queue: Arc<ArrayQueue<VideoFrame>>,
|
||||
video_sender: MediaSender,
|
||||
audio_sender: MediaSender,
|
||||
receiver: mpsc::UnboundedReceiver<Data>,
|
||||
@@ -44,7 +50,6 @@ pub struct Remote<T: InvokeUiSession> {
|
||||
// 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>,
|
||||
remove_jobs: HashMap<i32, RemoveJob>,
|
||||
@@ -57,24 +62,28 @@ pub struct Remote<T: InvokeUiSession> {
|
||||
frame_count: Arc<AtomicUsize>,
|
||||
video_format: CodecFormat,
|
||||
elevation_requested: bool,
|
||||
fps_control: FpsControl,
|
||||
decode_fps: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl<T: InvokeUiSession> Remote<T> {
|
||||
pub fn new(
|
||||
handler: Session<T>,
|
||||
video_queue: Arc<ArrayQueue<VideoFrame>>,
|
||||
video_sender: MediaSender,
|
||||
audio_sender: MediaSender,
|
||||
receiver: mpsc::UnboundedReceiver<Data>,
|
||||
sender: mpsc::UnboundedSender<Data>,
|
||||
frame_count: Arc<AtomicUsize>,
|
||||
decode_fps: Arc<AtomicUsize>,
|
||||
) -> Self {
|
||||
Self {
|
||||
handler,
|
||||
video_queue,
|
||||
video_sender,
|
||||
audio_sender,
|
||||
receiver,
|
||||
sender,
|
||||
old_clipboard: Default::default(),
|
||||
read_jobs: Vec::new(),
|
||||
write_jobs: Vec::new(),
|
||||
remove_jobs: Default::default(),
|
||||
@@ -89,6 +98,8 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
stop_voice_call_sender: None,
|
||||
voice_call_request_timestamp: None,
|
||||
elevation_requested: false,
|
||||
fps_control: Default::default(),
|
||||
decode_fps,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,9 +121,11 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((mut peer, direct)) => {
|
||||
Ok((mut peer, direct, pk)) => {
|
||||
self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready
|
||||
self.handler.set_connection_info(direct, false);
|
||||
self.handler
|
||||
.set_fingerprint(crate::common::pk_to_fingerprint(pk.unwrap_or_default()));
|
||||
|
||||
// just build for now
|
||||
#[cfg(not(windows))]
|
||||
@@ -136,6 +149,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
let mut rx_clip_client = rx_clip_client_lock.lock().await;
|
||||
|
||||
let mut status_timer = time::interval(Duration::new(1, 0));
|
||||
let mut fps_instant = Instant::now();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -213,9 +227,18 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
_ = status_timer.tick() => {
|
||||
let speed = self.data_count.swap(0, Ordering::Relaxed);
|
||||
self.fps_control();
|
||||
let elapsed = fps_instant.elapsed().as_millis();
|
||||
if elapsed < 1000 {
|
||||
continue;
|
||||
}
|
||||
fps_instant = Instant::now();
|
||||
let mut speed = self.data_count.swap(0, Ordering::Relaxed);
|
||||
speed = speed * 1000 / elapsed as usize;
|
||||
let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32);
|
||||
let fps = self.frame_count.swap(0, Ordering::Relaxed) as _;
|
||||
let mut fps = self.frame_count.swap(0, Ordering::Relaxed) as _;
|
||||
// Correcting the inaccuracy of status_timer
|
||||
fps = fps * 1000 / elapsed as i32;
|
||||
self.handler.update_quality_status(QualityStatus {
|
||||
speed:Some(speed),
|
||||
fps:Some(fps),
|
||||
@@ -283,62 +306,69 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
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 (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());
|
||||
// iOS does not have this server.
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
{
|
||||
// Create a channel to receive error or closed message
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(err) => {
|
||||
if err == TryRecvError::Empty {
|
||||
// ignore
|
||||
} else {
|
||||
log::debug!("Failed to record local audio channel: {}", err);
|
||||
}
|
||||
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();
|
||||
}
|
||||
Some(message::Union::Misc(misc)) => {
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc.clone());
|
||||
tx_audio.send(Data::Message(msg)).ok();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(err) => {
|
||||
if err == TryRecvError::Empty {
|
||||
// ignore
|
||||
} else {
|
||||
log::debug!("Failed to record local audio channel: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Some(tx)
|
||||
});
|
||||
return Some(tx);
|
||||
}
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_msg_from_ui(&mut self, data: Data, peer: &mut Stream) -> bool {
|
||||
@@ -351,9 +381,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
allow_err!(peer.send(&msg).await);
|
||||
return false;
|
||||
}
|
||||
Data::Login((password, remember)) => {
|
||||
Data::Login((os_username, os_password, password, remember)) => {
|
||||
self.handler
|
||||
.handle_login_from_ui(password, remember, peer)
|
||||
.handle_login_from_ui(os_username, os_password, password, remember, peer)
|
||||
.await;
|
||||
}
|
||||
Data::ToggleClipboardFile => {
|
||||
@@ -807,6 +837,63 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_key_frame(vf: &VideoFrame) -> bool {
|
||||
use video_frame::Union::*;
|
||||
match &vf.union {
|
||||
Some(vf) => match vf {
|
||||
Vp8s(f) | Vp9s(f) | H264s(f) | H265s(f) => f.frames.iter().any(|e| e.key),
|
||||
_ => false,
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn fps_control(&mut self) {
|
||||
let len = self.video_queue.len();
|
||||
let ctl = &mut self.fps_control;
|
||||
// Current full speed decoding fps
|
||||
let decode_fps = self.decode_fps.load(std::sync::atomic::Ordering::Relaxed);
|
||||
// 500ms
|
||||
let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 };
|
||||
if len < debounce || decode_fps == 0 {
|
||||
return;
|
||||
}
|
||||
// First setting , or the length of the queue still increases after setting, or exceed the size of the last setting again
|
||||
if ctl.set_times < 10 // enough
|
||||
&& (ctl.set_times == 0
|
||||
|| (len > ctl.last_queue_size && ctl.last_set_instant.elapsed().as_secs() > 30))
|
||||
{
|
||||
// 80% fps to ensure decoding is faster than encoding
|
||||
let mut custom_fps = decode_fps as i32 * 4 / 5;
|
||||
if custom_fps < 1 {
|
||||
custom_fps = 1;
|
||||
}
|
||||
// send custom fps
|
||||
let mut misc = Misc::new();
|
||||
misc.set_option(OptionMessage {
|
||||
custom_fps,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.sender.send(Data::Message(msg)).ok();
|
||||
ctl.last_queue_size = len;
|
||||
ctl.set_times += 1;
|
||||
ctl.last_set_instant = Instant::now();
|
||||
}
|
||||
// send refresh
|
||||
if ctl.refresh_times < 10 // enough
|
||||
&& (len > self.video_queue.capacity() / 2
|
||||
&& (ctl.refresh_times == 0 || ctl.last_refresh_instant.elapsed().as_secs() > 30))
|
||||
{
|
||||
// Refresh causes client set_display, left frames cause flickering.
|
||||
while let Some(_) = self.video_queue.pop() {}
|
||||
self.handler.refresh_video();
|
||||
ctl.refresh_times += 1;
|
||||
ctl.last_refresh_instant = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&data) {
|
||||
match msg_in.union {
|
||||
@@ -825,7 +912,15 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
self.video_sender.send(MediaData::VideoFrame(vf)).ok();
|
||||
if Self::contains_key_frame(&vf) {
|
||||
while let Some(_) = self.video_queue.pop() {}
|
||||
self.video_sender
|
||||
.send(MediaData::VideoFrame(Box::new(vf)))
|
||||
.ok();
|
||||
} else {
|
||||
self.video_queue.force_push(vf);
|
||||
self.video_sender.send(MediaData::VideoQueue).ok();
|
||||
}
|
||||
}
|
||||
Some(message::Union::Hash(hash)) => {
|
||||
self.handler
|
||||
@@ -842,30 +937,30 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.handler.handle_peer_info(pi);
|
||||
self.check_clipboard_file_context();
|
||||
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(),
|
||||
)));
|
||||
Client::try_start_clipboard(Some(
|
||||
crate::client::ClientClipboardContext {
|
||||
cfg: self.handler.get_permission_config(),
|
||||
tx: self.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()
|
||||
{
|
||||
if let Some(msg_out) = Client::get_current_text_clipboard_msg() {
|
||||
let sender = self.sender.clone();
|
||||
let permission_config = self.handler.get_permission_config();
|
||||
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() {
|
||||
sender.send(Data::Message(msg_out)).ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if self.handler.is_file_transfer() {
|
||||
@@ -886,7 +981,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
Some(message::Union::Clipboard(cb)) => {
|
||||
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));
|
||||
update_clipboard(cb, Some(&crate::client::get_old_clipboard_text()));
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
let content = if cb.compress {
|
||||
@@ -1203,6 +1298,22 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
#[cfg(feature = "flutter")]
|
||||
self.handler.switch_back(&self.handler.id);
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(misc::Union::PluginRequest(p)) => {
|
||||
allow_err!(crate::plugin::handle_server_event(&p.id, &p.content));
|
||||
// to-do: show message box on UI when error occurs?
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(misc::Union::PluginResponse(p)) => {
|
||||
let name = if p.name.is_empty() {
|
||||
"plugin".to_string()
|
||||
} else {
|
||||
p.name
|
||||
};
|
||||
self.handler.msgbox("custom-nocancel", &name, &p.msg, "");
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::TestDelay(t)) => {
|
||||
@@ -1210,7 +1321,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
Some(message::Union::AudioFrame(frame)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_audio.v {
|
||||
self.audio_sender.send(MediaData::AudioFrame(frame)).ok();
|
||||
self.audio_sender
|
||||
.send(MediaData::AudioFrame(Box::new(frame)))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
Some(message::Union::FileAction(action)) => match action.union {
|
||||
@@ -1223,6 +1336,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
},
|
||||
Some(message::Union::MessageBox(msgbox)) => {
|
||||
let mut link = msgbox.link;
|
||||
// Links from the remote side must be verified.
|
||||
if !link.starts_with("rustdesk://") {
|
||||
if let Some(v) = hbb_common::config::HELPER_URL.get(&link as &str) {
|
||||
link = v.to_string();
|
||||
@@ -1456,3 +1570,23 @@ impl RemoveJob {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FpsControl {
|
||||
last_queue_size: usize,
|
||||
set_times: usize,
|
||||
refresh_times: usize,
|
||||
last_set_instant: Instant,
|
||||
last_refresh_instant: Instant,
|
||||
}
|
||||
|
||||
impl Default for FpsControl {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
last_queue_size: Default::default(),
|
||||
set_times: Default::default(),
|
||||
refresh_times: Default::default(),
|
||||
last_set_instant: Instant::now(),
|
||||
last_refresh_instant: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,7 +755,7 @@ lazy_static::lazy_static! {
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref IS_X11: bool = "x11" == hbb_common::platform::linux::get_display_server();
|
||||
pub static ref IS_X11: bool = hbb_common::platform::linux::is_x11_or_headless();
|
||||
}
|
||||
|
||||
pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> String {
|
||||
@@ -782,6 +782,7 @@ pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> Strin
|
||||
/// 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) {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
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]);
|
||||
@@ -801,6 +802,9 @@ pub fn decode64<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, base64::DecodeError
|
||||
}
|
||||
|
||||
pub async fn get_key(sync: bool) -> String {
|
||||
#[cfg(target_os = "ios")]
|
||||
let mut key = Config::get_option("key");
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
let mut key = if sync {
|
||||
Config::get_option("key")
|
||||
} else {
|
||||
@@ -818,3 +822,35 @@ pub async fn get_key(sync: bool) -> String {
|
||||
}
|
||||
key
|
||||
}
|
||||
|
||||
pub fn is_peer_version_ge(v: &str) -> bool {
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
if let Some(session) = crate::ui::CUR_SESSION.lock().unwrap().as_ref() {
|
||||
return session.get_peer_version() >= hbb_common::get_version_number(v);
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(session) = crate::flutter::SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&*crate::flutter::CUR_SESSION_ID.read().unwrap())
|
||||
{
|
||||
return session.get_peer_version() >= hbb_common::get_version_number(v);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn pk_to_fingerprint(pk: Vec<u8>) -> String {
|
||||
let s: String = pk.iter().map(|u| format!("{:02x}", u)).collect();
|
||||
s.chars()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
if i > 0 && i % 4 == 0 {
|
||||
format!(" {}", c)
|
||||
} else {
|
||||
format!("{}", c)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::platform::breakdown_callback;
|
||||
use hbb_common::log;
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::platform::register_breakdown_handler;
|
||||
|
||||
@@ -8,6 +11,7 @@ use hbb_common::platform::register_breakdown_handler;
|
||||
/// [Note]
|
||||
/// If it returns [`None`], then the process will terminate, and flutter gui will not be started.
|
||||
/// If it returns [`Some`], then the process will continue, and flutter gui will be started.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn core_main() -> Option<Vec<String>> {
|
||||
let mut args = Vec::new();
|
||||
let mut flutter_args = Vec::new();
|
||||
@@ -38,6 +42,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
register_breakdown_handler(breakdown_callback);
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -140,10 +145,6 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
args.len() > 1,
|
||||
));
|
||||
return None;
|
||||
} else if args[0] == "--extract" {
|
||||
#[cfg(feature = "with_rc")]
|
||||
hbb_common::allow_err!(crate::rc::extract_resources(&args[1]));
|
||||
return None;
|
||||
} else if args[0] == "--install-cert" {
|
||||
#[cfg(windows)]
|
||||
hbb_common::allow_err!(crate::platform::windows::install_cert(&args[1]));
|
||||
@@ -223,6 +224,11 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
// call connection manager to establish connections
|
||||
// meanwhile, return true to call flutter window to show control panel
|
||||
crate::ui_interface::start_option_status_sync();
|
||||
} else if args[0] == "--cm-no-ui" {
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::flutter::connection_manager::start_cm_no_ui();
|
||||
return None;
|
||||
}
|
||||
}
|
||||
//_async_logger_holder.map(|x| x.flush());
|
||||
|
||||
130
src/flutter.rs
130
src/flutter.rs
@@ -3,18 +3,19 @@ use crate::{
|
||||
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,
|
||||
};
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
use hbb_common::{
|
||||
dlopen::{
|
||||
symbor::{Library, Symbol},
|
||||
Error as LibError,
|
||||
},
|
||||
libc::c_void,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
@@ -28,20 +29,20 @@ use std::{
|
||||
|
||||
/// tag "main" for [Desktop Main Page] and [Mobile (Client and Server)] (the mobile don't need multiple windows, only one global event stream is needed)
|
||||
/// tag "cm" only for [Desktop CM Page]
|
||||
pub(super) const APP_TYPE_MAIN: &str = "main";
|
||||
pub(crate) const APP_TYPE_MAIN: &str = "main";
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(super) const APP_TYPE_CM: &str = "cm";
|
||||
pub(crate) const APP_TYPE_CM: &str = "cm";
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub(super) const APP_TYPE_CM: &str = "main";
|
||||
pub(crate) const APP_TYPE_CM: &str = "main";
|
||||
|
||||
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";
|
||||
pub(crate) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
|
||||
pub(crate) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
||||
pub(crate) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub 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
|
||||
pub(crate) static ref CUR_SESSION_ID: RwLock<String> = Default::default();
|
||||
pub(crate) static ref SESSIONS: RwLock<HashMap<String, Session<FlutterHandler>>> = Default::default();
|
||||
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"))]
|
||||
@@ -145,6 +146,8 @@ pub struct FlutterHandler {
|
||||
notify_rendered: Arc<RwLock<bool>>,
|
||||
renderer: Arc<RwLock<VideoRenderer>>,
|
||||
peer_info: Arc<RwLock<PeerInfo>>,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
hooks: Arc<RwLock<HashMap<String, crate::api::SessionHook>>>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
@@ -156,6 +159,8 @@ pub struct FlutterHandler {
|
||||
pub rgba: Arc<RwLock<Vec<u8>>>,
|
||||
pub rgba_valid: Arc<AtomicBool>,
|
||||
peer_info: Arc<RwLock<PeerInfo>>,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
hooks: Arc<RwLock<HashMap<String, crate::api::SessionHook>>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
@@ -218,7 +223,7 @@ impl VideoRenderer {
|
||||
}
|
||||
|
||||
pub fn on_rgba(&self, rgba: &Vec<u8>) {
|
||||
if self.ptr == usize::default() {
|
||||
if self.ptr == usize::default() || self.width == 0 || self.height == 0 {
|
||||
return;
|
||||
}
|
||||
if let Some(func) = &self.on_rgba_func {
|
||||
@@ -244,17 +249,21 @@ impl FlutterHandler {
|
||||
///
|
||||
/// * `name` - The name of the event.
|
||||
/// * `event` - Fields of the event content.
|
||||
fn push_event(&self, name: &str, event: Vec<(&str, &str)>) {
|
||||
pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) -> Option<bool> {
|
||||
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
|
||||
assert!(h.get("name").is_none());
|
||||
h.insert("name", name);
|
||||
let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned());
|
||||
if let Some(stream) = &*self.event_stream.read().unwrap() {
|
||||
stream.add(EventToUI::Event(out));
|
||||
}
|
||||
Some(
|
||||
self.event_stream
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()?
|
||||
.add(EventToUI::Event(out)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn close_event_stream(&mut self) {
|
||||
pub(crate) 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()));
|
||||
@@ -276,6 +285,28 @@ impl FlutterHandler {
|
||||
serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(crate) fn add_session_hook(&self, key: String, hook: crate::api::SessionHook) -> bool {
|
||||
let mut hooks = self.hooks.write().unwrap();
|
||||
if hooks.contains_key(&key) {
|
||||
// Already has the hook with this key.
|
||||
return false;
|
||||
}
|
||||
let _ = hooks.insert(key, hook);
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(crate) fn remove_session_hook(&self, key: &String) -> bool {
|
||||
let mut hooks = self.hooks.write().unwrap();
|
||||
if !hooks.contains_key(key) {
|
||||
// The hook with this key does not found.
|
||||
return false;
|
||||
}
|
||||
let _ = hooks.remove(key);
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
pub fn register_texture(&mut self, ptr: usize) {
|
||||
@@ -364,6 +395,10 @@ impl InvokeUiSession for FlutterHandler {
|
||||
);
|
||||
}
|
||||
|
||||
fn set_fingerprint(&self, fingerprint: String) {
|
||||
self.push_event("fingerprint", vec![("fingerprint", &fingerprint)]);
|
||||
}
|
||||
|
||||
fn job_error(&self, id: i32, err: String, file_num: i32) {
|
||||
self.push_event(
|
||||
"job_error",
|
||||
@@ -509,6 +544,7 @@ impl InvokeUiSession for FlutterHandler {
|
||||
("features", &features),
|
||||
("current_display", &pi.current_display.to_string()),
|
||||
("resolutions", &resolutions),
|
||||
("platform_additions", &pi.platform_additions),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -599,7 +635,7 @@ impl InvokeUiSession for FlutterHandler {
|
||||
}
|
||||
|
||||
fn on_voice_call_closed(&self, reason: &str) {
|
||||
self.push_event("on_voice_call_closed", [("reason", reason)].into())
|
||||
let _res = self.push_event("on_voice_call_closed", [("reason", reason)].into());
|
||||
}
|
||||
|
||||
fn on_voice_call_waiting(&self) {
|
||||
@@ -814,8 +850,20 @@ pub mod connection_manager {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn start_cm_no_ui() {
|
||||
start_listen_ipc(false);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn start_listen_ipc_thread() {
|
||||
start_listen_ipc(true);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn start_listen_ipc(new_thread: bool) {
|
||||
use crate::ui_cm_interface::{start_ipc, ConnectionManager};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -824,7 +872,11 @@ pub mod connection_manager {
|
||||
let cm = ConnectionManager {
|
||||
ui_handler: FlutterHandler {},
|
||||
};
|
||||
std::thread::spawn(move || start_ipc(cm));
|
||||
if new_thread {
|
||||
std::thread::spawn(move || start_ipc(cm));
|
||||
} else {
|
||||
start_ipc(cm);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
@@ -949,6 +1001,7 @@ pub fn session_next_rgba(id: *const char) {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
pub fn session_register_texture(id: *const char, ptr: usize) {
|
||||
@@ -960,6 +1013,35 @@ pub fn session_register_texture(id: *const char, ptr: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
pub fn session_register_texture(_id: *const char, _ptr: usize) {}
|
||||
|
||||
#[inline]
|
||||
pub fn push_session_event(peer: &str, name: &str, event: Vec<(&str, &str)>) -> Option<bool> {
|
||||
SESSIONS.read().unwrap().get(peer)?.push_event(name, event)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_global_event(channel: &str, event: String) -> Option<bool> {
|
||||
Some(GLOBAL_EVENT_STREAM.read().unwrap().get(channel)?.add(event))
|
||||
}
|
||||
|
||||
pub fn start_global_event_stream(s: StreamSink<String>, app_type: String) -> ResultType<()> {
|
||||
if let Some(_) = GLOBAL_EVENT_STREAM
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(app_type.clone(), s)
|
||||
{
|
||||
log::warn!(
|
||||
"Global event stream of type {} is started before, but now removed",
|
||||
app_type
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop_global_event_stream(app_type: String) {
|
||||
let _ = GLOBAL_EVENT_STREAM.write().unwrap().remove(&app_type);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ use crate::{
|
||||
ui_interface::{self, *},
|
||||
};
|
||||
use flutter_rust_bridge::{StreamSink, SyncReturn};
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::allow_err;
|
||||
use hbb_common::{
|
||||
config::{self, LocalConfig, PeerConfig, PeerInfoSerde, ONLINE},
|
||||
fs, log,
|
||||
@@ -48,32 +51,20 @@ fn initialize(app_dir: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn start_global_event_stream(s: StreamSink<String>, app_type: String) -> ResultType<()> {
|
||||
super::flutter::start_global_event_stream(s, app_type)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stop_global_event_stream(app_type: String) {
|
||||
super::flutter::stop_global_event_stream(app_type)
|
||||
}
|
||||
pub enum EventToUI {
|
||||
Event(String),
|
||||
Rgba,
|
||||
}
|
||||
|
||||
pub fn start_global_event_stream(s: StreamSink<String>, app_type: String) -> ResultType<()> {
|
||||
if let Some(_) = flutter::GLOBAL_EVENT_STREAM
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(app_type.clone(), s)
|
||||
{
|
||||
log::warn!(
|
||||
"Global event stream of type {} is started before, but now removed",
|
||||
app_type
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop_global_event_stream(app_type: String) {
|
||||
let _ = flutter::GLOBAL_EVENT_STREAM
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove(&app_type);
|
||||
}
|
||||
|
||||
pub fn host_stop_system_key_propagate(_stopped: bool) {
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::stop_system_key_propagate(_stopped);
|
||||
@@ -136,9 +127,15 @@ pub fn session_get_option(id: String, arg: String) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_login(id: String, password: String, remember: bool) {
|
||||
pub fn session_login(
|
||||
id: String,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: String,
|
||||
remember: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.login(password, remember);
|
||||
session.login(os_username, os_password, password, remember);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,14 +165,12 @@ pub fn session_reconnect(id: String, force_relay: bool) {
|
||||
}
|
||||
|
||||
pub fn session_toggle_option(id: String, value: String) {
|
||||
let mut is_found = false;
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
|
||||
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" {
|
||||
if SESSIONS.read().unwrap().get(&id).is_some() && value == "disable-clipboard" {
|
||||
crate::flutter::update_text_clipboard_required();
|
||||
}
|
||||
}
|
||||
@@ -328,20 +323,26 @@ pub fn session_switch_display(id: String, value: i32) {
|
||||
pub fn session_handle_flutter_key_event(
|
||||
id: String,
|
||||
name: String,
|
||||
keycode: i32,
|
||||
scancode: i32,
|
||||
platform_code: i32,
|
||||
position_code: 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, lock_modes, down_or_up);
|
||||
session.handle_flutter_key_event(
|
||||
&name,
|
||||
platform_code,
|
||||
position_code,
|
||||
lock_modes,
|
||||
down_or_up,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_enter_or_leave(id: String, enter: bool) {
|
||||
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 {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&_id) {
|
||||
if _enter {
|
||||
session.enter();
|
||||
} else {
|
||||
session.leave();
|
||||
@@ -735,20 +736,18 @@ pub fn main_load_recent_peers() {
|
||||
.drain(..)
|
||||
.map(|(id, _, p)| peer_to_map(id, p))
|
||||
.collect();
|
||||
if let Some(s) = flutter::GLOBAL_EVENT_STREAM
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(flutter::APP_TYPE_MAIN)
|
||||
{
|
||||
let data = HashMap::from([
|
||||
("name", "load_recent_peers".to_owned()),
|
||||
(
|
||||
"peers",
|
||||
serde_json::ser::to_string(&peers).unwrap_or("".to_owned()),
|
||||
),
|
||||
]);
|
||||
s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned()));
|
||||
};
|
||||
|
||||
let data = HashMap::from([
|
||||
("name", "load_recent_peers".to_owned()),
|
||||
(
|
||||
"peers",
|
||||
serde_json::ser::to_string(&peers).unwrap_or("".to_owned()),
|
||||
),
|
||||
]);
|
||||
let _res = flutter::push_global_event(
|
||||
flutter::APP_TYPE_MAIN,
|
||||
serde_json::ser::to_string(&data).unwrap_or("".to_owned()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,38 +785,33 @@ pub fn main_load_fav_peers() {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if let Some(s) = flutter::GLOBAL_EVENT_STREAM
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(flutter::APP_TYPE_MAIN)
|
||||
{
|
||||
let data = HashMap::from([
|
||||
("name", "load_fav_peers".to_owned()),
|
||||
(
|
||||
"peers",
|
||||
serde_json::ser::to_string(&peers).unwrap_or("".to_owned()),
|
||||
),
|
||||
]);
|
||||
s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned()));
|
||||
};
|
||||
|
||||
let data = HashMap::from([
|
||||
("name", "load_fav_peers".to_owned()),
|
||||
(
|
||||
"peers",
|
||||
serde_json::ser::to_string(&peers).unwrap_or("".to_owned()),
|
||||
),
|
||||
]);
|
||||
let _res = flutter::push_global_event(
|
||||
flutter::APP_TYPE_MAIN,
|
||||
serde_json::ser::to_string(&data).unwrap_or("".to_owned()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_load_lan_peers() {
|
||||
if let Some(s) = flutter::GLOBAL_EVENT_STREAM
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(flutter::APP_TYPE_MAIN)
|
||||
{
|
||||
let data = HashMap::from([
|
||||
("name", "load_lan_peers".to_owned()),
|
||||
(
|
||||
"peers",
|
||||
serde_json::to_string(&get_lan_peers()).unwrap_or_default(),
|
||||
),
|
||||
]);
|
||||
s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned()));
|
||||
};
|
||||
let data = HashMap::from([
|
||||
("name", "load_lan_peers".to_owned()),
|
||||
(
|
||||
"peers",
|
||||
serde_json::to_string(&get_lan_peers()).unwrap_or_default(),
|
||||
),
|
||||
]);
|
||||
let _res = flutter::push_global_event(
|
||||
flutter::APP_TYPE_MAIN,
|
||||
serde_json::ser::to_string(&data).unwrap_or("".to_owned()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn main_remove_discovered(id: String) {
|
||||
@@ -831,20 +825,21 @@ fn main_broadcast_message(data: &HashMap<&str, &str>) {
|
||||
flutter::APP_TYPE_DESKTOP_PORT_FORWARD,
|
||||
];
|
||||
|
||||
let event = serde_json::ser::to_string(&data).unwrap_or("".to_owned());
|
||||
for app in apps {
|
||||
if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(app) {
|
||||
s.add(serde_json::ser::to_string(data).unwrap_or("".to_owned()));
|
||||
};
|
||||
let _res = flutter::push_global_event(app, event.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_change_theme(dark: String) {
|
||||
main_broadcast_message(&HashMap::from([("name", "theme"), ("dark", &dark)]));
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
send_to_cm(&crate::ipc::Data::Theme(dark));
|
||||
}
|
||||
|
||||
pub fn main_change_language(lang: String) {
|
||||
main_broadcast_message(&HashMap::from([("name", "language"), ("lang", &lang)]));
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
send_to_cm(&crate::ipc::Data::Language(lang));
|
||||
}
|
||||
|
||||
@@ -931,6 +926,10 @@ pub fn main_get_permanent_password() -> String {
|
||||
ui_interface::permanent_password()
|
||||
}
|
||||
|
||||
pub fn main_get_fingerprint() -> String {
|
||||
get_fingerprint()
|
||||
}
|
||||
|
||||
pub fn main_get_online_statue() -> i64 {
|
||||
ONLINE.lock().unwrap().values().max().unwrap_or(&0).clone()
|
||||
}
|
||||
@@ -971,6 +970,13 @@ pub fn main_has_hwcodec() -> SyncReturn<bool> {
|
||||
SyncReturn(has_hwcodec())
|
||||
}
|
||||
|
||||
pub fn main_supported_hwdecodings() -> SyncReturn<String> {
|
||||
let decoding = supported_hwdecodings();
|
||||
let msg = HashMap::from([("h264", decoding.0), ("h265", decoding.1)]);
|
||||
|
||||
SyncReturn(serde_json::ser::to_string(&msg).unwrap_or("".to_owned()))
|
||||
}
|
||||
|
||||
pub fn main_is_root() -> bool {
|
||||
is_root()
|
||||
}
|
||||
@@ -1056,10 +1062,10 @@ pub fn session_send_note(id: String, note: String) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_supported_hwcodec(id: String) -> String {
|
||||
pub fn session_alternative_codecs(id: String) -> String {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
let (h264, h265) = session.supported_hwcodec();
|
||||
let msg = HashMap::from([("h264", h264), ("h265", h265)]);
|
||||
let (vp8, h264, h265) = session.alternative_codecs();
|
||||
let msg = HashMap::from([("vp8", vp8), ("h264", h264), ("h265", h265)]);
|
||||
serde_json::ser::to_string(&msg).unwrap_or("".to_owned())
|
||||
} else {
|
||||
String::new()
|
||||
@@ -1127,6 +1133,8 @@ pub fn main_get_mouse_time() -> f64 {
|
||||
}
|
||||
|
||||
pub fn main_wol(id: String) {
|
||||
// TODO: move send_wol outside.
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
crate::lan::send_wol(id)
|
||||
}
|
||||
|
||||
@@ -1136,10 +1144,12 @@ pub fn main_create_shortcut(_id: String) {
|
||||
}
|
||||
|
||||
pub fn cm_send_chat(conn_id: i32, msg: String) {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
crate::ui_cm_interface::send_chat(conn_id, msg);
|
||||
}
|
||||
|
||||
pub fn cm_login_res(conn_id: i32, res: bool) {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
if res {
|
||||
crate::ui_cm_interface::authorize(conn_id);
|
||||
} else {
|
||||
@@ -1148,22 +1158,29 @@ pub fn cm_login_res(conn_id: i32, res: bool) {
|
||||
}
|
||||
|
||||
pub fn cm_close_connection(conn_id: i32) {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
crate::ui_cm_interface::close(conn_id);
|
||||
}
|
||||
|
||||
pub fn cm_remove_disconnected_connection(conn_id: i32) {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
crate::ui_cm_interface::remove(conn_id);
|
||||
}
|
||||
|
||||
pub fn cm_check_click_time(conn_id: i32) {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
crate::ui_cm_interface::check_click_time(conn_id)
|
||||
}
|
||||
|
||||
pub fn cm_get_click_time() -> f64 {
|
||||
crate::ui_cm_interface::get_click_time() as _
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
return crate::ui_cm_interface::get_click_time() as _;
|
||||
#[cfg(any(target_os = "ios"))]
|
||||
return 0 as _;
|
||||
}
|
||||
|
||||
pub fn cm_switch_permission(conn_id: i32, name: String, enabled: bool) {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
crate::ui_cm_interface::switch_permission(conn_id, name, enabled)
|
||||
}
|
||||
|
||||
@@ -1172,10 +1189,12 @@ pub fn cm_can_elevate() -> SyncReturn<bool> {
|
||||
}
|
||||
|
||||
pub fn cm_elevate_portable(conn_id: i32) {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
crate::ui_cm_interface::elevate_portable(conn_id);
|
||||
}
|
||||
|
||||
pub fn cm_switch_back(conn_id: i32) {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
crate::ui_cm_interface::switch_back(conn_id);
|
||||
}
|
||||
|
||||
@@ -1196,21 +1215,19 @@ unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *c
|
||||
}
|
||||
|
||||
fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) {
|
||||
if let Some(s) = flutter::GLOBAL_EVENT_STREAM
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(flutter::APP_TYPE_MAIN)
|
||||
{
|
||||
let data = HashMap::from([
|
||||
("name", "callback_query_onlines".to_owned()),
|
||||
("onlines", onlines.join(",")),
|
||||
("offlines", offlines.join(",")),
|
||||
]);
|
||||
s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned()));
|
||||
};
|
||||
let data = HashMap::from([
|
||||
("name", "callback_query_onlines".to_owned()),
|
||||
("onlines", onlines.join(",")),
|
||||
("offlines", offlines.join(",")),
|
||||
]);
|
||||
let _res = flutter::push_global_event(
|
||||
flutter::APP_TYPE_MAIN,
|
||||
serde_json::ser::to_string(&data).unwrap_or("".to_owned()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn query_onlines(ids: Vec<String>) {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
crate::rendezvous_mediator::query_online_states(ids, handle_query_onlines)
|
||||
}
|
||||
|
||||
@@ -1379,6 +1396,67 @@ pub fn send_url_scheme(_url: String) {
|
||||
std::thread::spawn(move || crate::handle_url_scheme(_url));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_event(_id: String, _event: Vec<u8>) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
allow_err!(crate::plugin::handle_ui_event(&_id, &_event));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_get_session_option(_id: String, _peer: String, _key: String) -> SyncReturn<Option<String>> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
return SyncReturn(crate::plugin::PeerConfig::get(&_id, &_peer, &_key));
|
||||
}
|
||||
#[cfg(any(
|
||||
not(feature = "plugin_framework"),
|
||||
target_os = "android",
|
||||
target_os = "ios"
|
||||
))]
|
||||
{
|
||||
return SyncReturn(None);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_set_session_option(_id: String, _peer: String, _key: String, _value: String) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
crate::plugin::PeerConfig::set(&_id, &_peer, &_key, &_value);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_get_local_option(_id: String, _key: String) -> SyncReturn<Option<String>> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
return SyncReturn(crate::plugin::LocalConfig::get(&_id, &_key));
|
||||
}
|
||||
#[cfg(any(
|
||||
not(feature = "plugin_framework"),
|
||||
target_os = "android",
|
||||
target_os = "ios"
|
||||
))]
|
||||
{
|
||||
return SyncReturn(None);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_set_local_option(_id: String, _key: String, _value: String) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
crate::plugin::LocalConfig::set(&_id, &_key, &_value);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use hbb_common::{config, log};
|
||||
@@ -1405,7 +1483,7 @@ pub mod server_side {
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startService(
|
||||
env: JNIEnv,
|
||||
_env: JNIEnv,
|
||||
_class: JClass,
|
||||
) {
|
||||
log::debug!("startService from jvm");
|
||||
|
||||
@@ -6,24 +6,28 @@ use hbb_common::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
use crate::Connection;
|
||||
|
||||
const TIME_HEARTBEAT: Duration = Duration::from_secs(30);
|
||||
const TIME_CONN: Duration = Duration::from_secs(3);
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref SENDER : Mutex<broadcast::Sender<Vec<i32>>> = Mutex::new(start_hbbs_sync());
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn start() {
|
||||
let _sender = SENDER.lock().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
pub fn signal_receiver() -> broadcast::Receiver<Vec<i32>> {
|
||||
SENDER.lock().unwrap().subscribe()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
fn start_hbbs_sync() -> broadcast::Sender<Vec<i32>> {
|
||||
let (tx, _rx) = broadcast::channel::<Vec<i32>>(16);
|
||||
std::thread::spawn(move || start_hbbs_sync_async());
|
||||
@@ -36,6 +40,7 @@ pub struct StrategyOptions {
|
||||
pub extra: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn start_hbbs_sync_async() {
|
||||
tokio::spawn(async move {
|
||||
|
||||
12
src/ipc.rs
12
src/ipc.rs
@@ -383,6 +383,12 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
));
|
||||
} else if name == "rendezvous_servers" {
|
||||
value = Some(Config::get_rendezvous_servers().join(","));
|
||||
} else if name == "fingerprint" {
|
||||
value = if Config::get_key_confirmed() {
|
||||
Some(crate::common::pk_to_fingerprint(Config::get_key_pair().1))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
} else {
|
||||
value = None;
|
||||
}
|
||||
@@ -690,6 +696,12 @@ pub fn get_permanent_password() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_fingerprint() -> String {
|
||||
get_config("fingerprint")
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn set_permanent_password(v: String) -> ResultType<()> {
|
||||
Config::set_permanent_password(&v);
|
||||
set_config("permanent-password", v)
|
||||
|
||||
324
src/keyboard.rs
324
src/keyboard.rs
@@ -1,22 +1,24 @@
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::client::get_key_state;
|
||||
use crate::common::GrabState;
|
||||
#[cfg(feature = "flutter")]
|
||||
use crate::flutter::{CUR_SESSION_ID, SESSIONS};
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::platform::windows::get_char_by_vk;
|
||||
use crate::platform::windows::{get_char_from_vk, get_unicode_from_vk};
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
use crate::ui::CUR_SESSION;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::{client::get_key_state, common::GrabState};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::log;
|
||||
use hbb_common::message_proto::*;
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
use rdev::KeyCode;
|
||||
use rdev::{Event, EventType, Key};
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use std::time::SystemTime;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{Arc, Mutex},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -71,6 +73,7 @@ pub mod client {
|
||||
super::start_grab_loop();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn change_grab_status(state: GrabState) {
|
||||
match state {
|
||||
GrabState::Ready => {}
|
||||
@@ -177,6 +180,7 @@ pub mod client {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn lock_screen() {
|
||||
send_key_event(&event_lock_screen());
|
||||
}
|
||||
@@ -196,6 +200,7 @@ pub mod client {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn ctrl_alt_del() {
|
||||
send_key_event(&event_ctrl_alt_del());
|
||||
}
|
||||
@@ -227,8 +232,8 @@ pub fn start_grab_loop() {
|
||||
}
|
||||
|
||||
let mut _keyboard_mode = KeyboardMode::Map;
|
||||
let _scan_code = event.scan_code;
|
||||
let _code = event.code;
|
||||
let _scan_code = event.position_code;
|
||||
let _code = event.platform_code as KeyCode;
|
||||
let res = if KEYBOARD_HOOKED.load(Ordering::SeqCst) {
|
||||
_keyboard_mode = client::process_event(&event, None);
|
||||
if is_press {
|
||||
@@ -264,7 +269,7 @@ pub fn start_grab_loop() {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
unsafe {
|
||||
if _code as u32 == rdev::kVK_Option {
|
||||
if _code == rdev::kVK_Option {
|
||||
IS_LEFT_OPTION_DOWN = is_press;
|
||||
}
|
||||
}
|
||||
@@ -316,6 +321,7 @@ pub fn is_long_press(event: &Event) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn release_remote_keys() {
|
||||
// todo!: client quit suddenly, how to release keys?
|
||||
let to_release = TO_RELEASE.lock().unwrap().clone();
|
||||
@@ -333,19 +339,117 @@ pub fn get_keyboard_mode_enum() -> KeyboardMode {
|
||||
match client::get_keyboard_mode().as_str() {
|
||||
"map" => KeyboardMode::Map,
|
||||
"translate" => KeyboardMode::Translate,
|
||||
_ => KeyboardMode::Legacy,
|
||||
"legacy" => KeyboardMode::Legacy,
|
||||
_ => {
|
||||
// Set "map" as default mode if version >= 1.2.0.
|
||||
if crate::is_peer_version_ge("1.2.0") {
|
||||
KeyboardMode::Map
|
||||
} else {
|
||||
KeyboardMode::Legacy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn add_numlock_capslock_with_lock_modes(key_event: &mut KeyEvent, lock_modes: i32) {
|
||||
pub fn is_modifier(key: &rdev::Key) -> bool {
|
||||
matches!(
|
||||
key,
|
||||
Key::ShiftLeft
|
||||
| Key::ShiftRight
|
||||
| Key::ControlLeft
|
||||
| Key::ControlRight
|
||||
| Key::MetaLeft
|
||||
| Key::MetaRight
|
||||
| Key::Alt
|
||||
| Key::AltGr
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn is_numpad_rdev_key(key: &rdev::Key) -> bool {
|
||||
matches!(
|
||||
key,
|
||||
Key::Kp0
|
||||
| Key::Kp1
|
||||
| Key::Kp2
|
||||
| Key::Kp3
|
||||
| Key::Kp4
|
||||
| Key::Kp5
|
||||
| Key::Kp6
|
||||
| Key::Kp7
|
||||
| Key::Kp8
|
||||
| Key::Kp9
|
||||
| Key::KpMinus
|
||||
| Key::KpMultiply
|
||||
| Key::KpDivide
|
||||
| Key::KpPlus
|
||||
| Key::KpDecimal
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn is_letter_rdev_key(key: &rdev::Key) -> bool {
|
||||
matches!(
|
||||
key,
|
||||
Key::KeyA
|
||||
| Key::KeyB
|
||||
| Key::KeyC
|
||||
| Key::KeyD
|
||||
| Key::KeyE
|
||||
| Key::KeyF
|
||||
| Key::KeyG
|
||||
| Key::KeyH
|
||||
| Key::KeyI
|
||||
| Key::KeyJ
|
||||
| Key::KeyK
|
||||
| Key::KeyL
|
||||
| Key::KeyM
|
||||
| Key::KeyN
|
||||
| Key::KeyO
|
||||
| Key::KeyP
|
||||
| Key::KeyQ
|
||||
| Key::KeyR
|
||||
| Key::KeyS
|
||||
| Key::KeyT
|
||||
| Key::KeyU
|
||||
| Key::KeyV
|
||||
| Key::KeyW
|
||||
| Key::KeyX
|
||||
| Key::KeyY
|
||||
| Key::KeyZ
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn is_numpad_key(event: &Event) -> bool {
|
||||
matches!(event.event_type, EventType::KeyPress(key) | EventType::KeyRelease(key) if is_numpad_rdev_key(&key))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn is_letter_key(event: &Event) -> bool {
|
||||
matches!(event.event_type, EventType::KeyPress(key) | EventType::KeyRelease(key) if is_letter_rdev_key(&key))
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn parse_add_lock_modes_modifiers(
|
||||
key_event: &mut KeyEvent,
|
||||
lock_modes: i32,
|
||||
is_numpad_key: bool,
|
||||
is_letter_key: bool,
|
||||
) {
|
||||
const CAPS_LOCK: i32 = 1;
|
||||
const NUM_LOCK: i32 = 2;
|
||||
// const SCROLL_LOCK: i32 = 3;
|
||||
if lock_modes & (1 << CAPS_LOCK) != 0 {
|
||||
if is_letter_key && (lock_modes & (1 << CAPS_LOCK) != 0) {
|
||||
key_event.modifiers.push(ControlKey::CapsLock.into());
|
||||
}
|
||||
if lock_modes & (1 << NUM_LOCK) != 0 {
|
||||
if is_numpad_key && lock_modes & (1 << NUM_LOCK) != 0 {
|
||||
key_event.modifiers.push(ControlKey::NumLock.into());
|
||||
}
|
||||
// if lock_modes & (1 << SCROLL_LOCK) != 0 {
|
||||
@@ -354,11 +458,11 @@ fn add_numlock_capslock_with_lock_modes(key_event: &mut KeyEvent, lock_modes: i3
|
||||
}
|
||||
|
||||
#[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) {
|
||||
fn add_lock_modes_modifiers(key_event: &mut KeyEvent, is_numpad_key: bool, is_letter_key: bool) {
|
||||
if is_letter_key && get_key_state(enigo::Key::CapsLock) {
|
||||
key_event.modifiers.push(ControlKey::CapsLock.into());
|
||||
}
|
||||
if get_key_state(enigo::Key::NumLock) {
|
||||
if is_numpad_key && get_key_state(enigo::Key::NumLock) {
|
||||
key_event.modifiers.push(ControlKey::NumLock.into());
|
||||
}
|
||||
}
|
||||
@@ -405,7 +509,7 @@ fn update_modifiers_state(event: &Event) {
|
||||
pub fn event_to_key_events(
|
||||
event: &Event,
|
||||
keyboard_mode: KeyboardMode,
|
||||
lock_modes: Option<i32>,
|
||||
_lock_modes: Option<i32>,
|
||||
) -> Vec<KeyEvent> {
|
||||
let mut key_event = KeyEvent::new();
|
||||
update_modifiers_state(event);
|
||||
@@ -424,7 +528,12 @@ pub fn event_to_key_events(
|
||||
peer.retain(|c| !c.is_whitespace());
|
||||
|
||||
key_event.mode = keyboard_mode.into();
|
||||
let mut key_events = match keyboard_mode {
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let mut key_events;
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let key_events;
|
||||
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(),
|
||||
@@ -442,26 +551,30 @@ pub fn event_to_key_events(
|
||||
}
|
||||
};
|
||||
|
||||
if keyboard_mode != KeyboardMode::Translate {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let is_numpad_key = is_numpad_key(&event);
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if keyboard_mode != KeyboardMode::Translate || is_numpad_key {
|
||||
let is_letter_key = is_letter_key(&event);
|
||||
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);
|
||||
if let Some(lock_modes) = _lock_modes {
|
||||
parse_add_lock_modes_modifiers(key_event, lock_modes, is_numpad_key, is_letter_key);
|
||||
} else {
|
||||
add_numlock_capslock_status(key_event);
|
||||
add_lock_modes_modifiers(key_event, is_numpad_key, is_letter_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
key_events
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn event_type_to_event(event_type: EventType) -> Event {
|
||||
Event {
|
||||
event_type,
|
||||
time: SystemTime::now(),
|
||||
unicode: None,
|
||||
code: 0,
|
||||
scan_code: 0,
|
||||
platform_code: 0,
|
||||
position_code: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,7 +659,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec<KeyEv
|
||||
// when pressing AltGr, an extra VK_LCONTROL with a special
|
||||
// scancode with bit 9 set is sent, let's ignore this.
|
||||
#[cfg(windows)]
|
||||
if (event.scan_code >> 8) == 0xE0 {
|
||||
if (event.position_code >> 8) == 0xE0 {
|
||||
unsafe {
|
||||
IS_ALT_GR = true;
|
||||
}
|
||||
@@ -725,7 +838,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec<KeyEv
|
||||
events
|
||||
}
|
||||
|
||||
pub fn map_keyboard_mode(peer: &str, 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;
|
||||
@@ -737,56 +850,58 @@ pub fn map_keyboard_mode(peer: &str, event: &Event, mut key_event: KeyEvent) ->
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let keycode = match peer {
|
||||
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 {
|
||||
if event.position_code > 255 && (event.position_code >> 8) != 0xE0 {
|
||||
return None;
|
||||
}
|
||||
event.scan_code
|
||||
event.position_code
|
||||
}
|
||||
OS_LOWER_MACOS => {
|
||||
if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" {
|
||||
rdev::win_scancode_to_macos_iso_code(event.scan_code)?
|
||||
rdev::win_scancode_to_macos_iso_code(event.position_code)?
|
||||
} else {
|
||||
rdev::win_scancode_to_macos_code(event.scan_code)?
|
||||
rdev::win_scancode_to_macos_code(event.position_code)?
|
||||
}
|
||||
}
|
||||
_ => rdev::win_scancode_to_linux_code(event.scan_code)?,
|
||||
_ => rdev::win_scancode_to_linux_code(event.position_code)?,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
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 _)?,
|
||||
let keycode = match _peer {
|
||||
OS_LOWER_WINDOWS => rdev::macos_code_to_win_scancode(event.platform_code as _)?,
|
||||
OS_LOWER_MACOS => event.platform_code as _,
|
||||
_ => rdev::macos_code_to_linux_code(event.platform_code as _)?,
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
let keycode = match peer {
|
||||
OS_LOWER_WINDOWS => rdev::linux_code_to_win_scancode(event.code as _)?,
|
||||
let keycode = match _peer {
|
||||
OS_LOWER_WINDOWS => rdev::linux_code_to_win_scancode(event.position_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 _)?
|
||||
rdev::linux_code_to_macos_iso_code(event.position_code as _)?
|
||||
} else {
|
||||
rdev::linux_code_to_macos_code(event.code as _)?
|
||||
rdev::linux_code_to_macos_code(event.position_code as _)?
|
||||
}
|
||||
}
|
||||
_ => event.code as _,
|
||||
_ => event.position_code as _,
|
||||
};
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let keycode = 0;
|
||||
|
||||
key_event.set_chr(keycode);
|
||||
key_event.set_chr(keycode as _);
|
||||
Some(key_event)
|
||||
}
|
||||
|
||||
fn try_fill_unicode(event: &Event, key_event: &KeyEvent, events: &mut Vec<KeyEvent>) {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn try_fill_unicode(_peer: &str, 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());
|
||||
evt.down = true;
|
||||
events.push(evt);
|
||||
}
|
||||
}
|
||||
@@ -794,17 +909,50 @@ fn try_fill_unicode(event: &Event, key_event: &KeyEvent, events: &mut Vec<KeyEve
|
||||
None =>
|
||||
{
|
||||
#[cfg(target_os = "windows")]
|
||||
if is_hot_key_modifiers_down() && unsafe { !IS_0X021D_DOWN } {
|
||||
if let Some(chr) = get_char_by_vk(event.code as u32) {
|
||||
let mut evt = key_event.clone();
|
||||
evt.set_seq(chr.to_string());
|
||||
events.push(evt);
|
||||
if _peer == OS_LOWER_LINUX {
|
||||
if is_hot_key_modifiers_down() && unsafe { !IS_0X021D_DOWN } {
|
||||
if let Some(chr) = get_char_from_vk(event.platform_code as u32) {
|
||||
let mut evt = key_event.clone();
|
||||
evt.set_seq(chr.to_string());
|
||||
evt.down = true;
|
||||
events.push(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn try_file_win2win_hotkey(
|
||||
peer: &str,
|
||||
event: &Event,
|
||||
key_event: &KeyEvent,
|
||||
events: &mut Vec<KeyEvent>,
|
||||
) {
|
||||
if peer == OS_LOWER_WINDOWS && is_hot_key_modifiers_down() && unsafe { !IS_0X021D_DOWN } {
|
||||
let mut down = false;
|
||||
let win2win_hotkey = match event.event_type {
|
||||
EventType::KeyPress(..) => {
|
||||
down = true;
|
||||
if let Some(unicode) = get_unicode_from_vk(event.platform_code as u32) {
|
||||
Some((unicode as u32 & 0x0000FFFF) | (event.platform_code << 16))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
EventType::KeyRelease(..) => Some(event.platform_code << 16),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(code) = win2win_hotkey {
|
||||
let mut evt = key_event.clone();
|
||||
evt.set_win2win_hotkey(code);
|
||||
evt.down = down;
|
||||
events.push(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn is_hot_key_modifiers_down() -> bool {
|
||||
if rdev::get_modifier(Key::ControlLeft) || rdev::get_modifier(Key::ControlRight) {
|
||||
@@ -820,21 +968,33 @@ fn is_hot_key_modifiers_down() -> bool {
|
||||
}
|
||||
|
||||
#[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)
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
fn is_altgr(event: &Event) -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
if event.platform_code == 0xFE03 {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if unsafe { IS_0X021D_DOWN } && event.position_code == 0xE038 {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[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)
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
fn is_press(event: &Event) -> bool {
|
||||
matches!(event.event_type, EventType::KeyPress(_))
|
||||
}
|
||||
|
||||
// https://github.com/fufesou/rustdesk/wiki/Keyboard-mode----Translate-Mode
|
||||
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")]
|
||||
@@ -849,30 +1009,39 @@ pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if is_numpad_key(&event) {
|
||||
if let Some(evt) = map_keyboard_mode(peer, event, key_event) {
|
||||
events.push(evt);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
// ignore right option key
|
||||
if event.code as u32 == rdev::kVK_RightOption {
|
||||
if event.platform_code == rdev::kVK_RightOption as u32 {
|
||||
return events;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
if is_altgr(event) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if event.position_code == 0x021D {
|
||||
return events;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if matches!(event.event_type, EventType::KeyPress(_)) {
|
||||
try_fill_unicode(event, &key_event, &mut events);
|
||||
try_file_win2win_hotkey(peer, event, &key_event, &mut events);
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
if events.is_empty() && is_press(event) {
|
||||
try_fill_unicode(peer, event, &key_event, &mut events);
|
||||
}
|
||||
|
||||
// If AltGr is down, no need to send events other than unicode.
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
if IS_0X021D_DOWN {
|
||||
@@ -880,18 +1049,25 @@ pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
try_fill_unicode(peer, event, &key_event, &mut events);
|
||||
}
|
||||
|
||||
if events.is_empty() {
|
||||
if let Some(evt) = translate_key_code(peer, event, key_event) {
|
||||
if let Some(evt) = map_keyboard_mode(peer, event, key_event) {
|
||||
events.push(evt);
|
||||
}
|
||||
}
|
||||
events
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn keycode_to_rdev_key(keycode: u32) -> Key {
|
||||
#[cfg(target_os = "windows")]
|
||||
return rdev::win_key_from_scancode(keycode);
|
||||
#[cfg(target_os = "linux")]
|
||||
return rdev::linux_key_from_code(keycode);
|
||||
#[cfg(target_os = "macos")]
|
||||
return rdev::macos_key_from_code(keycode.try_into().unwrap_or_default());
|
||||
}
|
||||
|
||||
11
src/lan.rs
11
src/lan.rs
@@ -1,7 +1,9 @@
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::config::Config;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::bail,
|
||||
config::{self, Config, RENDEZVOUS_PORT},
|
||||
config::{self, RENDEZVOUS_PORT},
|
||||
log,
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::*,
|
||||
@@ -11,6 +13,7 @@ use hbb_common::{
|
||||
},
|
||||
ResultType,
|
||||
};
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs, UdpSocket},
|
||||
@@ -19,6 +22,7 @@ use std::{
|
||||
|
||||
type Message = RendezvousMessage;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(super) fn start_listening() -> ResultType<()> {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], get_broadcast_port()));
|
||||
let socket = std::net::UdpSocket::bind(addr)?;
|
||||
@@ -98,9 +102,9 @@ fn get_broadcast_port() -> u16 {
|
||||
(RENDEZVOUS_PORT + 3) as _
|
||||
}
|
||||
|
||||
fn get_mac(ip: &IpAddr) -> String {
|
||||
fn get_mac(_ip: &IpAddr) -> String {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Ok(mac) = get_mac_by_ip(ip) {
|
||||
if let Ok(mac) = get_mac_by_ip(_ip) {
|
||||
mac.to_string()
|
||||
} else {
|
||||
"".to_owned()
|
||||
@@ -119,6 +123,7 @@ fn get_all_ipv4s() -> ResultType<Vec<Ipv4Addr>> {
|
||||
Ok(ipv4s)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn get_mac_by_ip(ip: &IpAddr) -> ResultType<String> {
|
||||
for interface in default_net::get_interfaces() {
|
||||
match ip {
|
||||
|
||||
@@ -5,12 +5,12 @@ mod cn;
|
||||
mod cs;
|
||||
mod da;
|
||||
mod de;
|
||||
mod el;
|
||||
mod en;
|
||||
mod eo;
|
||||
mod es;
|
||||
mod fa;
|
||||
mod fr;
|
||||
mod el;
|
||||
mod hu;
|
||||
mod id;
|
||||
mod it;
|
||||
@@ -32,6 +32,7 @@ mod tr;
|
||||
mod tw;
|
||||
mod ua;
|
||||
mod vn;
|
||||
mod lt;
|
||||
|
||||
pub const LANGS: &[(&str, &str)] = &[
|
||||
("en", "English"),
|
||||
@@ -66,6 +67,7 @@ pub const LANGS: &[(&str, &str)] = &[
|
||||
("th", "ภาษาไทย"),
|
||||
("sl", "Slovenščina"),
|
||||
("ro", "Română"),
|
||||
("lt", "Lietuvių"),
|
||||
];
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -129,6 +131,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
|
||||
"th" => th::T.deref(),
|
||||
"sl" => sl::T.deref(),
|
||||
"ro" => ro::T.deref(),
|
||||
"lt" => lt::T.deref(),
|
||||
_ => en::T.deref(),
|
||||
};
|
||||
if let Some(v) = m.get(&name as &str) {
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", "此文件与对方的一致"),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", "浏览模式"),
|
||||
("login_linux_tip", "登录被控端的 Linux 账户,才能启用 X 桌面"),
|
||||
("verify_rustdesk_password_tip", "验证 RustDesk 密码"),
|
||||
("remember_account_tip", "记住此账户"),
|
||||
("os_account_desk_tip", "在无显示器的环境下,此账户用于登录被控系统,并启用桌面"),
|
||||
("OS Account", "系统账户"),
|
||||
("another_user_login_title_tip", "其他用户已登录"),
|
||||
("another_user_login_text_tip", "断开"),
|
||||
("xorg_not_found_title_tip", "Xorg 未安装"),
|
||||
("xorg_not_found_text_tip", "请安装 Xorg"),
|
||||
("no_desktop_title_tip", "desktop 未安装"),
|
||||
("no_desktop_text_tip", "请安装 desktop"),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", "指纹"),
|
||||
("Copy Fingerprint", "复制指纹"),
|
||||
("no fingerprints", "没有指纹"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Finished", "Fertiggestellt"),
|
||||
("Speed", "Geschwindigkeit"),
|
||||
("Custom Image Quality", "Benutzerdefinierte Bildqualität"),
|
||||
("Privacy mode", "Datenschutz-Modus"),
|
||||
("Privacy mode", "Datenschutzmodus"),
|
||||
("Block user input", "Benutzereingaben blockieren"),
|
||||
("Unblock user input", "Benutzereingaben freigeben"),
|
||||
("Adjust Window", "Fenster anpassen"),
|
||||
@@ -123,8 +123,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scrollbar", "Scroll-Leiste"),
|
||||
("ScrollAuto", "Automatisch scrollen"),
|
||||
("Good image quality", "Hohe Bildqualität"),
|
||||
("Balanced", "Ausgeglichen"),
|
||||
("Optimize reaction time", "Geschwindigkeit"),
|
||||
("Balanced", "Ausgeglichene Bildqualität"),
|
||||
("Optimize reaction time", "Reaktionszeit optimieren"),
|
||||
("Custom", "Benutzerdefiniert"),
|
||||
("Show remote cursor", "Entfernten Cursor anzeigen"),
|
||||
("Show quality monitor", "Qualitätsüberwachung anzeigen"),
|
||||
@@ -181,7 +181,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Disconnect", "Verbindung trennen"),
|
||||
("Allow using keyboard and mouse", "Verwendung von Maus und Tastatur zulassen"),
|
||||
("Allow using clipboard", "Verwendung der Zwischenablage zulassen"),
|
||||
("Allow hearing sound", "System-Audio übertragen"),
|
||||
("Allow hearing sound", "Soundübertragung zulassen"),
|
||||
("Allow file copy and paste", "Kopieren und Einfügen von Dateien zulassen"),
|
||||
("Connected", "Verbunden"),
|
||||
("Direct and encrypted connection", "Direkte und verschlüsselte Verbindung"),
|
||||
@@ -218,7 +218,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_tip", "Nur IPs auf der Whitelist können zugreifen."),
|
||||
("Login", "Anmelden"),
|
||||
("Verify", "Überprüfen"),
|
||||
("Remember me", "Login speichern"),
|
||||
("Remember me", "Login merken"),
|
||||
("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 erneut anzumelden."),
|
||||
@@ -278,13 +278,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Input Control", "Eingabesteuerung"),
|
||||
("Audio Capture", "Audioaufnahme"),
|
||||
("File Connection", "Dateiverbindung"),
|
||||
("Screen Connection", "Bildschirmanschluss"),
|
||||
("Screen Connection", "Bildschirmverbindung"),
|
||||
("Do you accept?", "Verbindung zulassen?"),
|
||||
("Open System Setting", "Systemeinstellung öffnen"),
|
||||
("How to get Android input permission?", "Wie erhalte ich eine Android-Eingabeberechtigung?"),
|
||||
("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_new_connection_tip", "möchte ihr Gerät steuern."),
|
||||
("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."),
|
||||
@@ -315,7 +315,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Start the screen sharing service on boot, requires special permissions", "Bildschirmfreigabedienst beim Booten starten, erfordert zusätzliche Berechtigungen"),
|
||||
("Connection not allowed", "Verbindung abgelehnt"),
|
||||
("Legacy mode", "Kompatibilitätsmodus"),
|
||||
("Map mode", "Kartenmodus"),
|
||||
("Map mode", "Zuordnungsmodus"),
|
||||
("Translate mode", "Übersetzungsmodus"),
|
||||
("Use permanent password", "Permanentes Passwort verwenden"),
|
||||
("Use both passwords", "Beide Passwörter verwenden"),
|
||||
@@ -357,7 +357,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable Audio", "Audio aktivieren"),
|
||||
("Unlock Network Settings", "Netzwerkeinstellungen entsperren"),
|
||||
("Server", "Server"),
|
||||
("Direct IP Access", "Direkter IP-Zugriff"),
|
||||
("Direct IP Access", "Direkter IP-Zugang"),
|
||||
("Proxy", "Proxy"),
|
||||
("Apply", "Anwenden"),
|
||||
("Disconnect all devices?", "Alle Geräte trennen?"),
|
||||
@@ -420,7 +420,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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 die Berechtigung \"Input Monitoring\" erteilen."),
|
||||
("config_input", "Um den entfernten Desktop mit der Tastatur steuern zu können, müssen Sie RustDesk die Berechtigung \"Eingabeüberwachung\" 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"),
|
||||
@@ -428,7 +428,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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."),
|
||||
("still_click_uac_tip", "Der entfernte Benutzer muss immer noch im UAC-Fenster von RustDesk auf \"Ja\" 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"),
|
||||
@@ -477,8 +477,26 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Empty Username", "Leerer Benutzername"),
|
||||
("Empty Password", "Leeres Passwort"),
|
||||
("Me", "Ich"),
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("identical_file_tip", "Diese Datei ist identisch mit der Datei der Gegenstelle."),
|
||||
("show_monitors_tip", "Monitore in der Symbolleiste anzeigen"),
|
||||
("View Mode", "Ansichtsmodus"),
|
||||
("login_linux_tip", "Sie müssen sich an einem entfernten Linux-Konto anmelden, um eine X-Desktop-Sitzung zu eröffnen."),
|
||||
("verify_rustdesk_password_tip", "RustDesk-Passwort bestätigen"),
|
||||
("remember_account_tip", "Dieses Konto merken"),
|
||||
("os_account_desk_tip", "Dieses Konto wird verwendet, um sich beim entfernten Betriebssystem anzumelden und die Desktop-Sitzung im Headless-Modus zu aktivieren."),
|
||||
("OS Account", "Betriebssystem-Konto"),
|
||||
("another_user_login_title_tip", "Ein anderer Benutzer ist bereits angemeldet."),
|
||||
("another_user_login_text_tip", "Trennen"),
|
||||
("xorg_not_found_title_tip", "Xorg nicht gefunden."),
|
||||
("xorg_not_found_text_tip", "Bitte installieren Sie Xorg."),
|
||||
("no_desktop_title_tip", "Es ist kein Desktop verfügbar."),
|
||||
("no_desktop_text_tip", "Bitte installieren Sie den GNOME-Desktop."),
|
||||
("No need to elevate", "Erhöhung der Rechte nicht erforderlich"),
|
||||
("System Sound", "Systemsound"),
|
||||
("Default", "Systemstandard"),
|
||||
("New RDP", "Neue RDP-Verbindung"),
|
||||
("Fingerprint", "Fingerabdruck"),
|
||||
("Copy Fingerprint", "Fingerabdruck kopieren"),
|
||||
("no fingerprints", "Keine Fingerabdrücke"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", "Το αρχείο είναι πανομοιότυπο με αυτό του άλλου υπολογιστή."),
|
||||
("show_monitors_tip", "Εμφάνιση οθονών στη γραμμή εργαλείων"),
|
||||
("View Mode", "Λειτουργία προβολής"),
|
||||
("login_linux_tip", "Απαιτείται είσοδος σε απομακρυσμένο λογαριασμό Linux για την ενεργοποίηση του περιβάλλον εργασίας Χ."),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -53,6 +53,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."),
|
||||
("empty_address_book_tip", "Oh dear, it appears that there are currently no peers listed in your address book."),
|
||||
("identical_file_tip", "This file is identical with the peer's one."),
|
||||
("show_monitors_tip", "Show monitors in toolbar")
|
||||
("show_monitors_tip", "Show monitors in toolbar."),
|
||||
("enter_rustdesk_passwd_tip", "Enter RustDesk password"),
|
||||
("remember_rustdesk_passwd_tip", "Remember RustDesk password"),
|
||||
("login_linux_tip", "You need to login to remote Linux account to enable a X desktop session"),
|
||||
("verify_rustdesk_password_tip", "Verify RustDesk password"),
|
||||
("remember_account_tip", "Remember this account"),
|
||||
("os_account_desk_tip", "This account is used to login the remote OS and enable the desktop session in headless"),
|
||||
("another_user_login_title_tip", "Another user already logged in"),
|
||||
("another_user_login_text_tip", "Disconnect"),
|
||||
("xorg_not_found_title_tip", "Xorg not found"),
|
||||
("xorg_not_found_text_tip", "Please install Xorg"),
|
||||
("no_desktop_title_tip", "No desktop is avaliable"),
|
||||
("no_desktop_text_tip", "Please install GNOME desktop"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("This PC", "Este PC"),
|
||||
("or", "o"),
|
||||
("Continue with", "Continuar con"),
|
||||
("Elevate", "Elevar"),
|
||||
("Elevate", "Elevar privilegios"),
|
||||
("Zoom cursor", "Ampliar cursor"),
|
||||
("Accept sessions via password", "Aceptar sesiones a través de contraseña"),
|
||||
("Accept sessions via click", "Aceptar sesiones a través de clic"),
|
||||
@@ -422,16 +422,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Always use software rendering", "Usar siempre renderizado por software"),
|
||||
("config_input", "Para controlar el escritorio remoto con el teclado necesitas dar a RustDesk permisos de \"Monitorización de entrada\"."),
|
||||
("config_microphone", "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."),
|
||||
("request_elevation_tip", "También puedes solicitar elevación de privilegios si hay alguien en el lado remoto."),
|
||||
("Wait", "Esperar"),
|
||||
("Elevation Error", "Error de elevación"),
|
||||
("Elevation Error", "Error de elevación de privilegios"),
|
||||
("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"),
|
||||
("Request Elevation", "Solicitar Elevación de privilegios"),
|
||||
("wait_accept_uac_tip", "Por favor espere a que el usuario remoto acepte el diálogo UAC."),
|
||||
("Elevate successfully", "Elevar con éxito"),
|
||||
("Elevate successfully", "Elevación de privilegios exitosa"),
|
||||
("uppercase", "mayúsculas"),
|
||||
("lowercase", "minúsculas"),
|
||||
("digit", "dígito"),
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", "Este archivo es idéntico al del par."),
|
||||
("show_monitors_tip", "Mostrar monitores en la barra de herramientas"),
|
||||
("View Mode", "Modo Vista"),
|
||||
("login_linux_tip", "Necesitas iniciar sesión con la cueneta del Linux remoto para activar una sesión de escritorio X"),
|
||||
("verify_rustdesk_password_tip", "Verificar la contraseña de RustDesk"),
|
||||
("remember_account_tip", "Recordar esta cuenta"),
|
||||
("os_account_desk_tip", "Esta cueneta se usa para iniciar sesión en el sistema operativo remoto y habilitar la sesión de escritorio en headless."),
|
||||
("OS Account", "Cuenta del SO"),
|
||||
("another_user_login_title_tip", "Otro usuario ya ha iniciado sesión"),
|
||||
("another_user_login_text_tip", "Desconectar"),
|
||||
("xorg_not_found_title_tip", "Xorg no hallado"),
|
||||
("xorg_not_found_text_tip", "Por favor, instala Xorg"),
|
||||
("no_desktop_title_tip", "No hay escritorio disponible"),
|
||||
("no_desktop_text_tip", "Por favor, instala GNOME Desktop"),
|
||||
("No need to elevate", "No es necesario elevar privilegios"),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", "Nuevo RDP"),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enter Remote ID", "شناسه از راه دور را وارد کنید"),
|
||||
("Enter your password", "زمر عبور خود را وارد کنید"),
|
||||
("Logging in...", "...در حال ورود"),
|
||||
("Enable RDP session sharing", "اشتراک گذاری جلسه RDP را فعال کنید"),
|
||||
("Enable RDP session sharing", "را فعال کنید RDP اشتراک گذاری جلسه"),
|
||||
("Auto Login", "ورود خودکار"),
|
||||
("Enable Direct IP Access", "را فعال کنید IP دسترسی مستقیم"),
|
||||
("Rename", "تغییر نام"),
|
||||
@@ -240,7 +240,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Remove from Favorites", "از علاقه مندی ها حذف شود"),
|
||||
("Empty", "موردی وجود ندارد"),
|
||||
("Invalid folder name", "نام پوشه نامعتبر است"),
|
||||
("Socks5 Proxy", "Socks5 Proxy"),
|
||||
("Socks5 Proxy", "Socks5 پروکسی"),
|
||||
("Hostname", "نام هاست"),
|
||||
("Discovered", "پیدا شده"),
|
||||
("install_daemon_tip", "برای شروع در هنگام راه اندازی، باید سرویس سیستم را نصب کنید"),
|
||||
@@ -353,9 +353,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Light", "روشن"),
|
||||
("Follow System", "پیروی از سیستم"),
|
||||
("Enable hardware codec", "فعال سازی کدک سخت افزاری"),
|
||||
("Unlock Security Settings", "آنلاک شدن تنظیمات امنیتی"),
|
||||
("Unlock Security Settings", "دسترسی کامل به تنظیمات امنیتی"),
|
||||
("Enable Audio", "فعال شدن صدا"),
|
||||
("Unlock Network Settings", "آنلاک شدن تنظیمات شبکه"),
|
||||
("Unlock Network Settings", "دسترسی کامل به تنظیمات شبکه"),
|
||||
("Server", "سرور"),
|
||||
("Direct IP Access", "IP دسترسی مستقیم به"),
|
||||
("Proxy", "پروکسی"),
|
||||
@@ -479,6 +479,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Me", "من"),
|
||||
("identical_file_tip", "این فایل با فایل همتا یکسان است."),
|
||||
("show_monitors_tip", "نمایش مانیتورها در نوار ابزار"),
|
||||
("View Mode", ""),
|
||||
("View Mode", "حالت مشاهده"),
|
||||
("login_linux_tip", "برای فعال کردن دسکتاپ X، باید به حساب لینوکس راه دور وارد شوید"),
|
||||
("verify_rustdesk_password_tip", "رمز عبور RustDesk را تأیید کنید"),
|
||||
("remember_account_tip", "این حساب را به خاطر بسپارید"),
|
||||
("os_account_desk_tip", "این حساب برای ورود به سیستم عامل راه دور و فعال کردن جلسه دسکتاپ در هدلس استفاده می شود"),
|
||||
("OS Account", "حساب کاربری سیستم عامل"),
|
||||
("another_user_login_title_tip", "کاربر دیگری قبلاً وارد شده است"),
|
||||
("another_user_login_text_tip", "قطع شدن"),
|
||||
("xorg_not_found_title_tip", "پیدا نشد Xorg"),
|
||||
("xorg_not_found_text_tip", "لطفا Xorg را نصب کنید"),
|
||||
("no_desktop_title_tip", "هیچ دسکتاپی در دسترس نیست"),
|
||||
("no_desktop_text_tip", "لطفا دسکتاپ گنوم را نصب کنید"),
|
||||
("No need to elevate", "نیازی به ارتقاء نیست"),
|
||||
("System Sound", "صدای سیستم"),
|
||||
("Default", "پیش فرض"),
|
||||
("New RDP", "ریموت جدید"),
|
||||
("Fingerprint", "اثر انگشت"),
|
||||
("Copy Fingerprint", "کپی کردن اثر انگشت"),
|
||||
("no fingerprints", "بدون اثر انگشت"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Closed manually by the peer", "Fermé manuellement par le pair"),
|
||||
("Enable remote configuration modification", "Autoriser la modification de la configuration à distance"),
|
||||
("Run without install", "Exécuter sans installer"),
|
||||
("Connect via relay", ""),
|
||||
("Connect via relay", "Connexion via relais"),
|
||||
("Always connect via relay", "Forcer la connexion relais"),
|
||||
("whitelist_tip", "Seule une IP de la liste blanche peut accéder à mon appareil"),
|
||||
("Login", "Connexion"),
|
||||
@@ -288,8 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_service_will_start_tip", "L'activation de la capture d'écran démarrera automatiquement le service, permettant à d'autres appareils de demander une connexion à partir de cet appareil."),
|
||||
("android_stop_service_tip", "La fermeture du service fermera automatiquement toutes les connexions établies."),
|
||||
("android_version_audio_tip", "La version actuelle d'Android ne prend pas en charge la capture audio, veuillez passer à Android 10 ou supérieur."),
|
||||
("android_start_service_tip", ""),
|
||||
("android_permission_may_not_change_tip", ""),
|
||||
("android_start_service_tip", "Appuyez sur [Démarrer le service] ou activez l'autorisation [Capture d'écran] pour démarrer le service de partage d'écran."),
|
||||
("android_permission_may_not_change_tip", "Les autorisations pour les connexions établies peuvent ne pas être prisent en compte instantanément ou avant la reconnection."),
|
||||
("Account", "Compte"),
|
||||
("Overwrite", "Écraser"),
|
||||
("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"),
|
||||
@@ -311,8 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
("Start on Boot", "Lancer au démarrage"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Lancer le service de partage d'écran au démarrage, nécessite des autorisations spéciales"),
|
||||
("Connection not allowed", "Connexion non autorisée"),
|
||||
("Legacy mode", "Mode hérité"),
|
||||
("Map mode", ""),
|
||||
@@ -348,7 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Security", "Sécurité"),
|
||||
("Theme", "Thème"),
|
||||
("Dark Theme", "Thème somble"),
|
||||
("Light Theme", ""),
|
||||
("Light Theme", "Thème clair"),
|
||||
("Dark", "Sombre"),
|
||||
("Light", "Clair"),
|
||||
("Follow System", "Suivi système"),
|
||||
@@ -421,7 +421,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
("config_microphone", "Pour discuter à distance, vous devez accorder à RustDesk les autorisations « Enregistrer l'audio »."),
|
||||
("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"),
|
||||
@@ -451,34 +451,52 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
("Set one-time password length", ""),
|
||||
("idd_driver_tip", ""),
|
||||
("confirm_idd_driver_tip", ""),
|
||||
("RDP Settings", ""),
|
||||
("Sort by", ""),
|
||||
("New Connection", ""),
|
||||
("Restore", ""),
|
||||
("Minimize", ""),
|
||||
("Maximize", ""),
|
||||
("Your Device", ""),
|
||||
("empty_recent_tip", ""),
|
||||
("empty_favorite_tip", ""),
|
||||
("empty_lan_tip", ""),
|
||||
("empty_address_book_tip", ""),
|
||||
("eg: admin", ""),
|
||||
("Empty Username", ""),
|
||||
("Empty Password", ""),
|
||||
("Me", ""),
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("Voice call", "Appel voix"),
|
||||
("Text chat", "Conversation textuelfle"),
|
||||
("Stop voice call", "Stopper l'appel voix"),
|
||||
("relay_hint_tip", "Il se peut qu'il ne doit pas possible de se connecter directement, vous pouvez essayer de vous connecter via un relais. \nEn outre, si vous souhaitez utiliser directement le relais, vous pouvez ajouter le suffixe \"/r\" à l'ID ou sélectionner l'option \"Toujours se connecter via le relais\" dans la fiche pair."),
|
||||
("Reconnect", "Se reconnecter"),
|
||||
("Codec", "Codec"),
|
||||
("Resolution", "Résolution"),
|
||||
("No transfers in progress", "Pas de transfert en cours"),
|
||||
("Set one-time password length", "Définir la longueur du mot de passe à usage unique"),
|
||||
("idd_driver_tip", "Installez le pilote d'affichage virtuel pour être utilisé lorsque vous n'avez pas d'affichage physique."),
|
||||
("confirm_idd_driver_tip", "L'option d'installation du pilote d'affichage virtuel est cochée. Notez qu'un certificat de test sera installé pour faire confiance au pilote d'affichage virtuel. Ce certificat de test ne sera utilisé que pour faire confiance aux pilotes Rustdesk."),
|
||||
("RDP Settings", "Configuration RDP"),
|
||||
("Sort by", "Trier par"),
|
||||
("New Connection", "Nouvelle connexion"),
|
||||
("Restore", "Restaurer"),
|
||||
("Minimize", "Minimiser"),
|
||||
("Maximize", "Maximiser"),
|
||||
("Your Device", "Votre appareil"),
|
||||
("empty_recent_tip", "Oups, pas de sessions récentes!\nIl est temps d'en prévoir une nouvelle."),
|
||||
("empty_favorite_tip", "Vous n'avez pas encore de pairs favoris?\nTrouvons quelqu'un avec qui vous connecter et ajoutez-le à vos favoris!"),
|
||||
("empty_lan_tip", "Oh non, il semble que nous n'ayons pas encore de pairs découverts."),
|
||||
("empty_address_book_tip", "Ouh là là! il semble qu'il n'y ait actuellement aucun pair répertorié dans votre carnet d'adresses."),
|
||||
("eg: admin", "ex: admin"),
|
||||
("Empty Username", "Nom d'utilisation non spécifié"),
|
||||
("Empty Password", "Mot de passe non spécifié"),
|
||||
("Me", "Moi"),
|
||||
("identical_file_tip", "Ce fichier est identique à celui du pair."),
|
||||
("show_monitors_tip", "Afficher les moniteurs dans la barre d'outils."),
|
||||
("View Mode", "Mode vue"),
|
||||
("login_linux_tip", "Se connecter au compte Linux distant"),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
342
src/lang/it.rs
342
src/lang/it.rs
@@ -2,18 +2,18 @@ lazy_static::lazy_static! {
|
||||
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Stato"),
|
||||
("Your Desktop", "Il tuo desktop"),
|
||||
("desk_tip", "Puoi accedere al tuo desktop usando l'ID e la password riportati qui."),
|
||||
("Your Desktop", "Questo desktop"),
|
||||
("desk_tip", "Puoi accedere a questo desktop usando l'ID e la password indicati qui sotto."),
|
||||
("Password", "Password"),
|
||||
("Ready", "Pronto"),
|
||||
("Established", "Stabilito"),
|
||||
("connecting_status", "Connessione alla rete RustDesk in corso..."),
|
||||
("Established", "Stabilita"),
|
||||
("connecting_status", "Connessione alla rete RustDesk..."),
|
||||
("Enable Service", "Abilita servizio"),
|
||||
("Start Service", "Avvia servizio"),
|
||||
("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 un desktop remoto"),
|
||||
("not_ready_status", "Non pronto. Verifica la connessione"),
|
||||
("Control Remote Desktop", "Controlla desktop remoto"),
|
||||
("Transfer File", "Trasferisci file"),
|
||||
("Connect", "Connetti"),
|
||||
("Recent Sessions", "Sessioni recenti"),
|
||||
@@ -22,38 +22,38 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("TCP Tunneling", "Tunnel TCP"),
|
||||
("Remove", "Rimuovi"),
|
||||
("Refresh random password", "Nuova password casuale"),
|
||||
("Set your own password", "Imposta la tua password"),
|
||||
("Set your own password", "Imposta la password"),
|
||||
("Enable Keyboard/Mouse", "Abilita tastiera/mouse"),
|
||||
("Enable Clipboard", "Abilita appunti"),
|
||||
("Enable File Transfer", "Abilita trasferimento file"),
|
||||
("Enable TCP Tunneling", "Abilita tunnel TCP"),
|
||||
("IP Whitelisting", "IP autorizzati"),
|
||||
("ID/Relay Server", "Server ID/Relay"),
|
||||
("Import Server Config", "Importa configurazione Server"),
|
||||
("Export Server Config", "Esporta configurazione Server"),
|
||||
("Import server configuration successfully", "Configurazione Server importata con successo"),
|
||||
("Export server configuration successfully", "Configurazione Server esportata con successo"),
|
||||
("Invalid server configuration", "Configurazione Server non valida"),
|
||||
("Import Server Config", "Importa configurazione server dagli appunti"),
|
||||
("Export Server Config", "Esporta configurazione server negli appunti"),
|
||||
("Import server configuration successfully", "Configurazione server importata completata"),
|
||||
("Export server configuration successfully", "Configurazione Server esportata completata"),
|
||||
("Invalid server configuration", "Configurazione server non valida"),
|
||||
("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%"),
|
||||
("Your new ID", "Il nuovo ID"),
|
||||
("length %min% to %max%", "lunghezza da %min% a %max%"),
|
||||
("starts with a letter", "inizia con una lettera"),
|
||||
("allowed characters", "caratteri consentiti"),
|
||||
("id_change_tip", "Puoi usare solo i caratteri a-z, A-Z, 0-9 e _ (underscore). Il primo carattere deve essere a-z o A-Z. La lunghezza deve essere fra 6 e 16 caratteri."),
|
||||
("Website", "Sito web"),
|
||||
("About", "Informazioni"),
|
||||
("Slogan_tip", "Fatta con il cuore in questo mondo caotico!"),
|
||||
("id_change_tip", "Puoi usare solo i caratteri a-z, A-Z, 0-9 e _ (sottolineato).\nIl primo carattere deve essere a-z o A-Z.\nLa lunghezza deve essere fra 6 e 16 caratteri."),
|
||||
("Website", "Sito web programma"),
|
||||
("About", "Info programma"),
|
||||
("Slogan_tip", "Realizzato con il cuore in questo mondo caotico!"),
|
||||
("Privacy Statement", "Informativa sulla privacy"),
|
||||
("Mute", "Silenzia"),
|
||||
("Build Date", "Data della build"),
|
||||
("Mute", "Audio off"),
|
||||
("Build Date", "Data build"),
|
||||
("Version", "Versione"),
|
||||
("Home", "Home"),
|
||||
("Audio Input", "Input audio"),
|
||||
("Audio Input", "Ingresso audio"),
|
||||
("Enhancements", "Miglioramenti"),
|
||||
("Hardware Codec", "Codifica Hardware"),
|
||||
("Adaptive Bitrate", "Bitrate Adattivo"),
|
||||
("Hardware Codec", "Codec hardware"),
|
||||
("Adaptive Bitrate", "Bitrate adattivo"),
|
||||
("ID Server", "ID server"),
|
||||
("Relay Server", "Server relay"),
|
||||
("API Server", "Server API"),
|
||||
@@ -68,65 +68,65 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Close", "Chiudi"),
|
||||
("Retry", "Riprova"),
|
||||
("OK", "OK"),
|
||||
("Password Required", "Password Richiesta"),
|
||||
("Please enter your password", "Inserisci la tua password"),
|
||||
("Password Required", "Password richiesta"),
|
||||
("Please enter your password", "Inserisci la password"),
|
||||
("Remember password", "Ricorda password"),
|
||||
("Wrong Password", "Password Errata"),
|
||||
("Wrong Password", "Password errata"),
|
||||
("Do you want to enter again?", "Vuoi riprovare?"),
|
||||
("Connection Error", "Errore di connessione"),
|
||||
("Error", "Errore"),
|
||||
("Reset by the peer", "Reimpostata dal peer"),
|
||||
("Connecting...", "Connessione..."),
|
||||
("Connection in progress. Please wait.", "Connessione in corso. Attendi."),
|
||||
("Please try 1 minute later", "Per favore riprova fra 1 minuto"),
|
||||
("Login Error", "Errore Login"),
|
||||
("Successful", "Successo"),
|
||||
("Connection in progress. Please wait.", "Connessione in corso..."),
|
||||
("Please try 1 minute later", "Riprova fra 1 minuto"),
|
||||
("Login Error", "Errore accesso"),
|
||||
("Successful", "Completato"),
|
||||
("Connected, waiting for image...", "Connesso, in attesa dell'immagine..."),
|
||||
("Name", "Nome"),
|
||||
("Type", "Tipo"),
|
||||
("Modified", "Modificato"),
|
||||
("Size", "Dimensione"),
|
||||
("Show Hidden Files", "Mostra file nascosti"),
|
||||
("Show Hidden Files", "Visualizza file nascosti"),
|
||||
("Receive", "Ricevi"),
|
||||
("Send", "Invia"),
|
||||
("Refresh File", "Aggiorna file"),
|
||||
("Local", "Locale"),
|
||||
("Remote", "Remote"),
|
||||
("Remote", "Remoto"),
|
||||
("Remote Computer", "Computer remoto"),
|
||||
("Local Computer", "Computer locale"),
|
||||
("Confirm Delete", "Conferma cancellazione"),
|
||||
("Delete", "Eliminare"),
|
||||
("Confirm Delete", "Conferma eliminazione"),
|
||||
("Delete", "Elimina"),
|
||||
("Properties", "Proprietà"),
|
||||
("Multi Select", "Selezione multipla"),
|
||||
("Select All", "Seleziona tutto"),
|
||||
("Unselect All", "Deseleziona tutto"),
|
||||
("Empty Directory", "Directory vuota"),
|
||||
("Not an empty directory", "Non una directory vuota"),
|
||||
("Are you sure you want to delete this file?", "Vuoi davvero eliminare questo file?"),
|
||||
("Are you sure you want to delete this empty directory?", "Sei sicuro di voler eliminare questa directory vuota?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Sei sicuro di voler eliminare il file di questa directory?"),
|
||||
("Empty Directory", "Cartella vuota"),
|
||||
("Not an empty directory", "Non è una cartella vuota"),
|
||||
("Are you sure you want to delete this file?", "Sei sicuro di voler eliminare questo file?"),
|
||||
("Are you sure you want to delete this empty directory?", "Sei sicuro di voler eliminare questa cartella vuota?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Sei sicuro di voler eliminare il file di questa cartella?"),
|
||||
("Do this for all conflicts", "Ricorca questa scelta per tutti i conflitti"),
|
||||
("This is irreversible!", "Questo è irreversibile!"),
|
||||
("Deleting", "Cancellazione di"),
|
||||
("Deleting", "Eliminazione di"),
|
||||
("files", "file"),
|
||||
("Waiting", "In attesa"),
|
||||
("Finished", "Terminato"),
|
||||
("Finished", "Completato"),
|
||||
("Speed", "Velocità"),
|
||||
("Custom Image Quality", "Qualità immagine personalizzata"),
|
||||
("Privacy mode", "Modalità privacy"),
|
||||
("Block user input", "Blocca l'input dell'utente"),
|
||||
("Unblock user input", "Sbloccare l'input dell'utente"),
|
||||
("Adjust Window", "Adatta la finestra"),
|
||||
("Block user input", "Blocca input utente"),
|
||||
("Unblock user input", "Sblocca input utente"),
|
||||
("Adjust Window", "Adatta finestra"),
|
||||
("Original", "Originale"),
|
||||
("Shrink", "Restringi"),
|
||||
("Stretch", "Allarga"),
|
||||
("Scrollbar", "Barra di scorrimento"),
|
||||
("Scrollbar", "Barra scorrimento"),
|
||||
("ScrollAuto", "Scorri automaticamente"),
|
||||
("Good image quality", "Qualità immagine migliore"),
|
||||
("Balanced", "Bilanciato"),
|
||||
("Optimize reaction time", "Ottimizza il tempo di reazione"),
|
||||
("Custom", "Personalizzato"),
|
||||
("Show remote cursor", "Mostra il cursore remoto"),
|
||||
("Good image quality", "Qualità immagine buona"),
|
||||
("Balanced", "Bilanciata qualità/velocità"),
|
||||
("Optimize reaction time", "Ottimizza tempo reazione"),
|
||||
("Custom", "Qualità personalizzata"),
|
||||
("Show remote cursor", "Visualizza cursore remoto"),
|
||||
("Show quality monitor", "Visualizza qualità video"),
|
||||
("Disable clipboard", "Disabilita appunti"),
|
||||
("Lock after session end", "Blocca al termine della sessione"),
|
||||
@@ -144,25 +144,25 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to connect via relay server", "Errore di connessione tramite il server relay"),
|
||||
("Failed to make direct connection to remote desktop", "Impossibile connettersi direttamente al desktop remoto"),
|
||||
("Set Password", "Imposta password"),
|
||||
("OS Password", "Password del sistema operativo"),
|
||||
("install_tip", "A causa del Controllo Account Utente, RustDesk potrebbe non funzionare correttamente come desktop remoto. Per evitare questo problema, fai click sul tasto qui sotto per installare RustDesk a livello di sistema."),
|
||||
("Click to upgrade", "Fai click per aggiornare"),
|
||||
("Click to download", "Cliquez per scaricare"),
|
||||
("Click to update", "Fare clic per aggiornare"),
|
||||
("OS Password", "Password sistema operativo"),
|
||||
("install_tip", "A causa del controllo account uUtente (UAC), RustDesk potrebbe non funzionare correttamente come desktop remoto.\nPer evitare questo problema, fai clic sul tasto qui sotto per installare RustDesk a livello di sistema."),
|
||||
("Click to upgrade", "Aggiorna"),
|
||||
("Click to download", "Download"),
|
||||
("Click to update", "Aggiorna"),
|
||||
("Configure", "Configura"),
|
||||
("config_acc", "Per controllare il tuo desktop dall'esterno, devi fornire a RustDesk il permesso \"Accessibilità\"."),
|
||||
("config_screen", "Per controllare il tuo desktop dall'esterno, devi fornire a RustDesk il permesso \"Registrazione schermo\"."),
|
||||
("config_acc", "Per controllare il desktop dall'esterno, devi fornire a RustDesk il permesso 'Accessibilità'."),
|
||||
("config_screen", "Per controllare il desktop dall'esterno, devi fornire a RustDesk il permesso 'Registrazione schermo'."),
|
||||
("Installing ...", "Installazione ..."),
|
||||
("Install", "Installa"),
|
||||
("Installation", "Installazione"),
|
||||
("Installation Path", "Percorso di installazione"),
|
||||
("Create start menu shortcuts", "Crea i collegamenti nel menu di avvio"),
|
||||
("Installation Path", "Percorso installazione"),
|
||||
("Create start menu shortcuts", "Crea i collegamenti nel menu Start"),
|
||||
("Create desktop icon", "Crea un'icona sul desktop"),
|
||||
("agreement_tip", "Avviando l'installazione, accetti i termini del contratto di licenza."),
|
||||
("Accept and Install", "Accetta e installa"),
|
||||
("End-user license agreement", "Contratto di licenza con l'utente finale"),
|
||||
("End-user license agreement", "Contratto di licenza utente finale"),
|
||||
("Generating ...", "Generazione ..."),
|
||||
("Your installation is lower version.", "La tua installazione non è aggiornata."),
|
||||
("Your installation is lower version.", "Questa installazione non è aggiornata."),
|
||||
("not_close_tcp_tip", "Non chiudere questa finestra mentre stai usando il tunnel"),
|
||||
("Listening ...", "In ascolto ..."),
|
||||
("Remote Host", "Host remoto"),
|
||||
@@ -172,9 +172,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Local Port", "Porta locale"),
|
||||
("Local Address", "Indirizzo locale"),
|
||||
("Change Local Port", "Cambia porta locale"),
|
||||
("setup_server_tip", "Per una connessione più veloce, configura un tuo server"),
|
||||
("Too short, at least 6 characters.", "Troppo breve, almeno 6 caratteri"),
|
||||
("The confirmation is not identical.", "La conferma non corrisponde"),
|
||||
("setup_server_tip", "Per una connessione più veloce, configura uno specifico server"),
|
||||
("Too short, at least 6 characters.", "Troppo corta, almeno 6 caratteri"),
|
||||
("The confirmation is not identical.", "La password di conferma non corrisponde"),
|
||||
("Permissions", "Permessi"),
|
||||
("Accept", "Accetta"),
|
||||
("Dismiss", "Rifiuta"),
|
||||
@@ -189,7 +189,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Direct and unencrypted connection", "Connessione diretta e non cifrata"),
|
||||
("Relayed and unencrypted connection", "Connessione tramite relay e non cifrata"),
|
||||
("Enter Remote ID", "Inserisci l'ID remoto"),
|
||||
("Enter your password", "Inserisci la tua password"),
|
||||
("Enter your password", "Inserisci la password"),
|
||||
("Logging in...", "Autenticazione..."),
|
||||
("Enable RDP session sharing", "Abilita la condivisione della sessione RDP"),
|
||||
("Auto Login", "Accesso automatico"),
|
||||
@@ -197,31 +197,31 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Rename", "Rinomina"),
|
||||
("Space", "Spazio"),
|
||||
("Create Desktop Shortcut", "Crea collegamento sul desktop"),
|
||||
("Change Path", "Cambia percorso"),
|
||||
("Change Path", "Modifica percorso"),
|
||||
("Create Folder", "Crea cartella"),
|
||||
("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 accesso non è supportata utilizzando Wayland"),
|
||||
("Login screen using Wayland is not supported", "La schermata di accesso non è supportata usando Wayland"),
|
||||
("Reboot required", "Riavvio necessario"),
|
||||
("Unsupported display server", "Display server non supportato"),
|
||||
("x11 expected", "x11 necessario"),
|
||||
("x11 expected", "necessario xll"),
|
||||
("Port", "Porta"),
|
||||
("Settings", "Impostazioni"),
|
||||
("Username", "Nome utente"),
|
||||
("Invalid port", "Numero di porta non valido"),
|
||||
("Invalid port", "Numero 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", "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"),
|
||||
("whitelist_tip", "Possono connettersi a questo desktop solo gli indirizzi IP autorizzati"),
|
||||
("Login", "Accedi"),
|
||||
("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."),
|
||||
("verification_tip", "È stato rilevato un nuovo dispositivo ed è stato inviato un codice di verifica all'indirizzo email registrato.\nPer continuare l'accesso inserisci il codice di verifica."),
|
||||
("Logout", "Esci"),
|
||||
("Tags", "Tag"),
|
||||
("Search ID", "Cerca ID"),
|
||||
@@ -243,10 +243,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Socks5 Proxy", "Proxy Socks5"),
|
||||
("Hostname", "Nome host"),
|
||||
("Discovered", "Rilevati"),
|
||||
("install_daemon_tip", "Per avviarsi all'accensione, è necessario installare il servizio di sistema."),
|
||||
("install_daemon_tip", "Per avviare il programma all'accensione, è necessario installarlo come servizio di sistema."),
|
||||
("Remote ID", "ID remoto"),
|
||||
("Paste", "Incolla"),
|
||||
("Paste here?", "Incolla qui?"),
|
||||
("Paste here?", "Incollare qui?"),
|
||||
("Are you sure to close the connection?", "Sei sicuro di voler chiudere la connessione?"),
|
||||
("Download new version", "Scarica nuova versione"),
|
||||
("Touch mode", "Modalità tocco"),
|
||||
@@ -264,9 +264,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Two-Finger Move", "Movimento con due dita"),
|
||||
("Canvas Move", "Sposta tela"),
|
||||
("Pinch to Zoom", "Pizzica per zoomare"),
|
||||
("Canvas Zoom", "Zoom canvas"),
|
||||
("Reset canvas", "Ripristina canvas"),
|
||||
("No permission of file transfer", "Nessun permesso di trasferimento di file"),
|
||||
("Canvas Zoom", "Zoom tela"),
|
||||
("Reset canvas", "Ripristina tela"),
|
||||
("No permission of file transfer", "Nessun permesso per il trasferimento file"),
|
||||
("Note", "Nota"),
|
||||
("Connection", "Connessione"),
|
||||
("Share Screen", "Condividi schermo"),
|
||||
@@ -275,68 +275,68 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("items", "Oggetti"),
|
||||
("Selected", "Selezionato"),
|
||||
("Screen Capture", "Cattura schermo"),
|
||||
("Input Control", "Controllo di input"),
|
||||
("Input Control", "Controllo input"),
|
||||
("Audio Capture", "Acquisizione audio"),
|
||||
("File Connection", "Connessione file"),
|
||||
("Screen Connection", "Connessione schermo"),
|
||||
("Do you accept?", "Accetti?"),
|
||||
("Open System Setting", "Apri impostazioni di sistema"),
|
||||
("How to get Android input permission?", "Come ottenere l'autorizzazione di input su Android?"),
|
||||
("android_input_permission_tip1", "Affinché un dispositivo remoto possa controllare il tuo dispositivo Android tramite mouse o tocco, devi consentire a RustDesk di utilizzare il servizio \"Accessibilità\"."),
|
||||
("android_input_permission_tip2", "Vai alla pagina delle impostazioni di sistema che si aprirà di seguito, trova e accedi a [Servizi installati], attiva il servizio [RustDesk Input]."),
|
||||
("android_new_connection_tip", "È stata ricevuta una nuova richiesta di controllo per il dispositivo corrente."),
|
||||
("How to get Android input permission?", "Come ottenere l'autorizzazione input in Android?"),
|
||||
("android_input_permission_tip1", "Affinché un dispositivo remoto possa controllare un dispositivo Android tramite mouse o tocco, devi consentire a RustDesk di usare il servizio 'Accessibilità'."),
|
||||
("android_input_permission_tip2", "Vai nella pagina delle impostazioni di sistema che si aprirà di seguito, trova e accedi a [Servizi installati], attiva il servizio [RustDesk Input]."),
|
||||
("android_new_connection_tip", "È stata ricevuta una nuova richiesta di controllo per il dispositivo attuale."),
|
||||
("android_service_will_start_tip", "L'attivazione di Cattura schermo avvierà automaticamente il servizio, consentendo ad altri dispositivi di richiedere una connessione da questo dispositivo."),
|
||||
("android_stop_service_tip", "La chiusura del servizio chiuderà automaticamente tutte le connessioni stabilite."),
|
||||
("android_version_audio_tip", "L'attuale versione di Android non supporta l'acquisizione audio, esegui l'upgrade ad Android 10 o versioni successive."),
|
||||
("android_start_service_tip", ""),
|
||||
("android_permission_may_not_change_tip", ""),
|
||||
("android_version_audio_tip", "L'attuale versione di Android non supporta l'acquisizione audio, esegui l'aggiornamento ad Android 10 o versioni successive."),
|
||||
("android_start_service_tip", "Per avviare il servizio di condivisione dello schermo seleziona [Avvia servizio] o abilita l'autorizzazione [Cattura schermo]."),
|
||||
("android_permission_may_not_change_tip", "Le autorizzazioni per le connessioni stabilite non possono essere modificate istantaneamente fino alla riconnessione."),
|
||||
("Account", "Account"),
|
||||
("Overwrite", "Sovrascrivi"),
|
||||
("This file exists, skip or overwrite this file?", "Questo file esiste, saltare o sovrascrivere questo file?"),
|
||||
("This file exists, skip or overwrite this file?", "Questo file esiste, vuoi ignorarlo o sovrascrivere questo file?"),
|
||||
("Quit", "Esci"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("Help", "Aiuto"),
|
||||
("Failed", "Fallito"),
|
||||
("Succeeded", "Successo"),
|
||||
("Someone turns on privacy mode, exit", "Qualcuno attiva la modalità privacy, esci"),
|
||||
("Succeeded", "Completato"),
|
||||
("Someone turns on privacy mode, exit", "Qualcuno ha attivato la modalità privacy, uscita"),
|
||||
("Unsupported", "Non supportato"),
|
||||
("Peer denied", "Peer negato"),
|
||||
("Please install plugins", "Si prega di installare i plugin"),
|
||||
("Peer exit", "Uscita tra pari"),
|
||||
("Please install plugins", "Installa i plugin"),
|
||||
("Peer exit", "Uscita peer"),
|
||||
("Failed to turn off", "Impossibile spegnere"),
|
||||
("Turned off", "Spegni"),
|
||||
("In privacy mode", "In modalità privacy"),
|
||||
("Out privacy mode", "Fuori modalità privacy"),
|
||||
("Language", "Linguaggio"),
|
||||
("Out privacy mode", "Uscita dalla modalità privacy"),
|
||||
("Language", "Lingua"),
|
||||
("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]."),
|
||||
("android_open_battery_optimizations_tip", "Se vuoi disabilitare questa funzione, vai nelle impostazioni dell'applicazione RustDesk, apri la sezione 'Batteria' e deseleziona '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"),
|
||||
("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"),
|
||||
("Translate mode", "Modalità di traduzione"),
|
||||
("Translate mode", "Modalità traduzione"),
|
||||
("Use permanent password", "Usa password permanente"),
|
||||
("Use both passwords", "Usa entrambe le password"),
|
||||
("Use both passwords", "Usa password monouso e permanente"),
|
||||
("Set permanent password", "Imposta password permanente"),
|
||||
("Enable Remote Restart", "Abilita riavvio da remoto"),
|
||||
("Allow remote restart", "Consenti riavvio da remoto"),
|
||||
("Restart Remote Device", "Riavvia dispositivo remoto"),
|
||||
("Are you sure you want to restart", "Sei sicuro di voler riavviare?"),
|
||||
("Restarting Remote Device", "Il dispositivo remoto si sta riavviando"),
|
||||
("remote_restarting_tip", "Riavviare il dispositivo remoto"),
|
||||
("remote_restarting_tip", "Riavvia il dispositivo remoto"),
|
||||
("Copied", "Copiato"),
|
||||
("Exit Fullscreen", "Esci dalla modalità schermo intero"),
|
||||
("Fullscreen", "A schermo intero"),
|
||||
("Mobile Actions", "Azioni mobili"),
|
||||
("Select Monitor", "Seleziona schermo"),
|
||||
("Control Actions", "Azioni di controllo"),
|
||||
("Display Settings", "Impostazioni di visualizzazione"),
|
||||
("Control Actions", "Azioni controllo"),
|
||||
("Display Settings", "Impostazioni visualizzazione"),
|
||||
("Ratio", "Rapporto"),
|
||||
("Image Quality", "Qualità dell'immagine"),
|
||||
("Scroll Style", "Stile di scorrimento"),
|
||||
("Show Menubar", "Mostra la barra dei menu"),
|
||||
("Image Quality", "Qualità immagine"),
|
||||
("Scroll Style", "Stile scorrimento"),
|
||||
("Show Menubar", "Visualizza barra menu"),
|
||||
("Hide Menubar", "nascondi la barra dei menu"),
|
||||
("Direct Connection", "Connessione diretta"),
|
||||
("Relay Connection", "Connessione relay"),
|
||||
@@ -347,91 +347,91 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("General", "Generale"),
|
||||
("Security", "Sicurezza"),
|
||||
("Theme", "Tema"),
|
||||
("Dark Theme", "Tema Scuro"),
|
||||
("Light Theme", ""),
|
||||
("Dark Theme", "Tema scuro"),
|
||||
("Light Theme", "Tema chiaro"),
|
||||
("Dark", "Scuro"),
|
||||
("Light", "Chiaro"),
|
||||
("Follow System", "Segui il sistema"),
|
||||
("Follow System", "Sistema"),
|
||||
("Enable hardware codec", "Abilita codec hardware"),
|
||||
("Unlock Security Settings", "Sblocca impostazioni di sicurezza"),
|
||||
("Unlock Security Settings", "Sblocca impostazioni sicurezza"),
|
||||
("Enable Audio", "Abilita audio"),
|
||||
("Unlock Network Settings", "Sblocca impostazioni di rete"),
|
||||
("Server", "Server"),
|
||||
("Direct IP Access", "Accesso IP diretto"),
|
||||
("Proxy", "Proxy"),
|
||||
("Apply", "Applica"),
|
||||
("Disconnect all devices?", "Disconnettere tutti i dispositivi?"),
|
||||
("Clear", "Ripulisci"),
|
||||
("Audio Input Device", "Dispositivo di input audio"),
|
||||
("Disconnect all devices?", "Vuoi disconnettere tutti i dispositivi?"),
|
||||
("Clear", "Azzera"),
|
||||
("Audio Input Device", "Dispositivo ingresso audio"),
|
||||
("Deny remote access", "Nega accesso remoto"),
|
||||
("Use IP Whitelisting", "Utilizza la whitelist di IP"),
|
||||
("Use IP Whitelisting", "Usa elenco IP autorizzati"),
|
||||
("Network", "Rete"),
|
||||
("Enable RDP", "Abilita RDP"),
|
||||
("Pin menubar", "Blocca la barra dei menu"),
|
||||
("Unpin menubar", "Sblocca la barra dei menu"),
|
||||
("Pin menubar", "Blocca barra menu"),
|
||||
("Unpin menubar", "Sblocca barra menu"),
|
||||
("Recording", "Registrazione"),
|
||||
("Directory", "Cartella"),
|
||||
("Automatically record incoming sessions", "Registra automaticamente le sessioni in entrata"),
|
||||
("Change", "Cambia"),
|
||||
("Start session recording", "Inizia registrazione della sessione"),
|
||||
("Stop session recording", "Ferma registrazione della sessione"),
|
||||
("Enable Recording Session", "Abilita registrazione della sessione"),
|
||||
("Allow recording session", "Permetti di registrare la sessione"),
|
||||
("Enable LAN Discovery", "Abilita il rilevamento della LAN"),
|
||||
("Deny LAN Discovery", "Nega il rilevamento della LAN"),
|
||||
("Change", "Modifica"),
|
||||
("Start session recording", "Inizia registrazione sessione"),
|
||||
("Stop session recording", "Ferma registrazione sessione"),
|
||||
("Enable Recording Session", "Abilita registrazione sessione"),
|
||||
("Allow recording session", "Permetti registrazione sessione"),
|
||||
("Enable LAN Discovery", "Abilita rilevamento LAN"),
|
||||
("Deny LAN Discovery", "Nega rilevamento LAN"),
|
||||
("Write a message", "Scrivi un messaggio"),
|
||||
("Prompt", "Richiede"),
|
||||
("Prompt", "Richiedi"),
|
||||
("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."),
|
||||
("elevated_foreground_window_tip", "La finestra attuale del desktop remoto richiede per funzionare privilegi più elevati, quindi non è possibile usare temporaneamente il mouse e la tastiera.\nÈ possibile chiedere all'utente remoto di ridurre a icona la finestra attuale o di selezionare il pulsante di elevazione nella finestra di gestione della connessione.\nPer evitare questo problema, ti consigliamo di installare il software nel dispositivo remoto."),
|
||||
("Disconnected", "Disconnesso"),
|
||||
("Other", "Altro"),
|
||||
("Confirm before closing multiple tabs", "Conferma prima di chiudere più schede"),
|
||||
("Keyboard Settings", "Impostazioni tastiera"),
|
||||
("Full Access", "Accesso completo"),
|
||||
("Screen Share", "Condivisione dello schermo"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland richiede Ubuntu 21.04 o successiva."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland richiede una versione superiore della distribuzione Linux. Prova X11 desktop o cambia il tuo sistema operativo."),
|
||||
("JumpLink", "View"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland richiede Ubuntu 21.04 o versione successiva."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland richiede una versione superiore della distribuzione Linux.\nProva X11 desktop o cambia il sistema operativo."),
|
||||
("JumpLink", "Vai a"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato peer)."),
|
||||
("Show RustDesk", "Mostra RustDesk"),
|
||||
("Show RustDesk", "Visualizza RustDesk"),
|
||||
("This PC", "Questo PC"),
|
||||
("or", "O"),
|
||||
("Continue with", "Continua con"),
|
||||
("Elevate", "Eleva"),
|
||||
("Zoom cursor", "Cursore zoom"),
|
||||
("Accept sessions via password", "Accetta sessioni via password"),
|
||||
("Accept sessions via click", "Accetta sessioni via click"),
|
||||
("Accept sessions via click", "Accetta sessioni via clic"),
|
||||
("Accept sessions via both", "Accetta sessioni con entrambi"),
|
||||
("Please wait for the remote side to accept your session request...", "Attendere che il lato remoto accetti la richiesta di sessione..."),
|
||||
("Please wait for the remote side to accept your session request...", "Attendi che il dispositivo remoto accetti la richiesta di sessione..."),
|
||||
("One-time Password", "Password monouso"),
|
||||
("Use one-time password", "Usa password monouso"),
|
||||
("One-time password length", "Lunghezza password monouso"),
|
||||
("Request access to your device", "Richiedi l'accesso al tuo dispositivo"),
|
||||
("Request access to your device", "Richiedi l'accesso al dispositivo"),
|
||||
("Hide connection management window", "Nascondi la finestra di gestione delle connessioni"),
|
||||
("hide_cm_tip", "Permetti di nascondere solo se si accettano sessioni con password permanente"),
|
||||
("wayland_experiment_tip", "Il supporto Wayland è in fase sperimentale, utilizza X11 se necessiti di un accesso stabile."),
|
||||
("wayland_experiment_tip", "Il supporto Wayland è in fase sperimentale, se vuoi un accesso stabile usa X11."),
|
||||
("Right click to select tabs", "Clic con il tasto destro per selezionare le schede"),
|
||||
("Skipped", "Saltato"),
|
||||
("Add to Address Book", "Aggiungi alla rubrica"),
|
||||
("Group", "Gruppo"),
|
||||
("Search", "Cerca"),
|
||||
("Closed manually by web console", "Chiudi manualmente dalla console Web"),
|
||||
("Local keyboard type", "Tipo di tastiera locale"),
|
||||
("Closed manually by web console", "Chiudi manualmente dalla console web"),
|
||||
("Local keyboard type", "Tipo tastiera locale"),
|
||||
("Select local keyboard type", "Seleziona il tipo di tastiera locale"),
|
||||
("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."),
|
||||
("software_render_tip", "Se disponi di una scheda grafica Nvidia e la finestra remota si chiude immediatamente dopo la connessione, l'installazione del driver aggiornato e la scelta di usare il rendering software possono aiutare.\nÈ necessario un riavvio del programma."),
|
||||
("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 input'."),
|
||||
("config_microphone", "Per poter chiamare, è necessario concedere l'autorizzazione a RustDesk 'Registra audio'."),
|
||||
("request_elevation_tip", "Se c'è qualcuno nel lato remoto è possibile richiedere l'elevazione."),
|
||||
("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."),
|
||||
("Ask the remote user for authentication", "Chiedi l'autenticazione all'utente remoto"),
|
||||
("Choose this if the remote account is administrator", "Scegli questa opzione se l'account remoto è amministratore"),
|
||||
("Transmit the username and password of administrator", "Trasmetti il nome utente e la password dell'amministratore"),
|
||||
("still_click_uac_tip", "Richiedi 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"),
|
||||
("wait_accept_uac_tip", "Attendi che l'utente remoto accetti la finestra di dialogo UAC."),
|
||||
("Elevate successfully", "Elevazione dei diritti effettuata correttamente"),
|
||||
("uppercase", "Maiuscola"),
|
||||
("lowercase", "Minuscola"),
|
||||
("digit", "Numero"),
|
||||
@@ -441,44 +441,62 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Medium", "Media"),
|
||||
("Strong", "Forte"),
|
||||
("Switch Sides", "Cambia lato"),
|
||||
("Please confirm if you want to share your desktop?", "Vuoi condividere il tuo desktop?"),
|
||||
("Please confirm if you want to share your desktop?", "Vuoi condividere il 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"),
|
||||
("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"),
|
||||
("Auto", "Automatico"),
|
||||
("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."),
|
||||
("Stop voice call", "Interrompi chiamata vocale"),
|
||||
("relay_hint_tip", "Se non è possibile connettersi direttamente, puoi provare a farlo tramite relay.\nInoltre, se si vuoi usare il relay al primo tentativo, è possibile aggiungere all'ID il suffisso '/r\' o selezionare nella scheda peer l'opzione 'Collegati sempre tramite relay'."),
|
||||
("Reconnect", "Riconnetti"),
|
||||
("Codec", "Codec"),
|
||||
("Resolution", "Risoluzione"),
|
||||
("No transfers in progress", "Nessun trasferimento in corso"),
|
||||
("Set one-time password length", "Imposta la lunghezza della password monouso"),
|
||||
("idd_driver_tip", "Installa il driver per lo schermo virtuale che sarà utilizzato quando non si dispone di schermi fisici."),
|
||||
("confirm_idd_driver_tip", "L'opzione per installare il driver per lo schermo virtuale è selezionata. Nota che un certificato di test sarà installato per l'attendibilità del driver dello schermo virtuale. Questo certificato di test verrà utilizzato solo per l'attendibilità dei driver di RustDesk."),
|
||||
("Set one-time password length", "Imposta lunghezza password monouso"),
|
||||
("idd_driver_tip", "Installa driver schermo virtuale che verrà usato quando non sono disponibili schermi fisici."),
|
||||
("confirm_idd_driver_tip", "È stata selezionata l'opzione per installare il driver schermo virtuale.\nNota che verrà installato un certificato di test per l'attendibilità del driver dello schermo virtuale.\nQuesto certificato di test verrà utilizzato solo per l'attendibilità dei driver di RustDesk."),
|
||||
("RDP Settings", "Impostazioni RDP"),
|
||||
("Sort by", "Ordina per"),
|
||||
("New Connection", "Nuova connessione"),
|
||||
("Restore", "Ripristina"),
|
||||
("Minimize", "Minimizza"),
|
||||
("Maximize", "Massimizza"),
|
||||
("Your Device", "Il tuo dispositivo"),
|
||||
("empty_recent_tip", "Oops, non c'è nessuna sessione recente!\nTempo di pianificarne una."),
|
||||
("empty_favorite_tip", "Ancora nessun peer?\nTrova qualcuno con cui connetterti e aggiungilo ai tuoi preferiti!"),
|
||||
("empty_lan_tip", "Oh no, sembra proprio che non abbiamo ancora rilevato nessun peer."),
|
||||
("empty_address_book_tip", "Oh diamine, sembra che per ora non ci siano peer nella tua rubrica."),
|
||||
("Your Device", "Questo dispositivo"),
|
||||
("empty_recent_tip", "Non c'è nessuna sessione recente!\nPianificane una."),
|
||||
("empty_favorite_tip", "Ancora nessun peer?\nTrova qualcuno con cui connetterti e aggiungilo ai preferiti!"),
|
||||
("empty_lan_tip", "Sembra proprio che non sia stato rilevato nessun peer."),
|
||||
("empty_address_book_tip", "Sembra che per ora nella rubrica non ci siano peer."),
|
||||
("eg: admin", "es: admin"),
|
||||
("Empty Username", "Nome Utente Vuoto"),
|
||||
("Empty Password", "Password Vuota"),
|
||||
("Empty Username", "Nome utente vuoto"),
|
||||
("Empty Password", "Password vuota"),
|
||||
("Me", "Io"),
|
||||
("identical_file_tip", "Questo file è identico a quello del peer."),
|
||||
("show_monitors_tip", "Mostra schermi nella barra degli strumenti"),
|
||||
("View Mode", ""),
|
||||
("show_monitors_tip", "Visualizza schermi nella barra strumenti"),
|
||||
("View Mode", "Modalità visualizzazione"),
|
||||
("login_linux_tip", "Accedi all'account Linux remoto"),
|
||||
("verify_rustdesk_password_tip", "Conferma password RustDesk"),
|
||||
("remember_account_tip", "Ricorda questo account"),
|
||||
("os_account_desk_tip", "Questo account viene usato per accedere al sistema operativo remoto e attivare la sessione desktop in modalità non presidiata."),
|
||||
("OS Account", "Account sistema operativo"),
|
||||
("another_user_login_title_tip", "È già loggato un altro utente."),
|
||||
("another_user_login_text_tip", "Separato"),
|
||||
("xorg_not_found_title_tip", "Xorg non trovato."),
|
||||
("xorg_not_found_text_tip", "Installa Xorg."),
|
||||
("no_desktop_title_tip", "Non c'è nessun desktop disponibile."),
|
||||
("no_desktop_text_tip", "Installa il desktop GNOME."),
|
||||
("No need to elevate", "Elevazione dei privilegi non richiesta"),
|
||||
("System Sound", "Dispositivo audio sistema"),
|
||||
("Default", "Predefinita"),
|
||||
("New RDP", "Nuovo RDP"),
|
||||
("Fingerprint", "Firma digitale"),
|
||||
("Copy Fingerprint", "Copia firma digitale"),
|
||||
("no fingerprints", "Nessuna firma digitale"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
502
src/lang/lt.rs
Normal file
502
src/lang/lt.rs
Normal file
@@ -0,0 +1,502 @@
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Būsena"),
|
||||
("Your Desktop", "Jūsų darbalaukis"),
|
||||
("desk_tip", "Jūsų darbalaukis pasiekiamas naudojant šį ID ir slaptažodį"),
|
||||
("Password", "Slaptažodis"),
|
||||
("Ready", "Pasiruošęs"),
|
||||
("Established", "Įsteigta"),
|
||||
("connecting_status", "Prisijungiama prie RustDesk tinklo..."),
|
||||
("Enable Service", "Įgalinti paslaugą"),
|
||||
("Start Service", "Pradėti paslaugą"),
|
||||
("Service is running", "Paslauga veikia"),
|
||||
("Service is not running", "Paslauga neveikia"),
|
||||
("not_ready_status", "Neprisijungęs. Patikrinkite ryšį."),
|
||||
("Control Remote Desktop", "Nuotolinio darbalaukio valdymas"),
|
||||
("Transfer File", "Perkelti failą"),
|
||||
("Connect", "Prisijungti"),
|
||||
("Recent Sessions", "Seansų istorija"),
|
||||
("Address Book", "Adresų knyga"),
|
||||
("Confirmation", "Patvirtinimas"),
|
||||
("TCP Tunneling", "TCP tuneliavimas"),
|
||||
("Remove", "Pašalinti"),
|
||||
("Refresh random password", "Atnaujinti atsitiktinį slaptažodį"),
|
||||
("Set your own password", "Nustatykite savo slaptažodį"),
|
||||
("Enable Keyboard/Mouse", "Įgalinti klaviatūrą/pelę"),
|
||||
("Enable Clipboard", "Įgalinti iškarpinę"),
|
||||
("Enable File Transfer", "Įgalinti failų perdavimą"),
|
||||
("Enable TCP Tunneling", "Įgalinti TCP tuneliavimą"),
|
||||
("IP Whitelisting", "IP baltasis sąrašas"),
|
||||
("ID/Relay Server", "ID / perdavimo serveris"),
|
||||
("Import Server Config", "Importuoti serverio konfigūraciją"),
|
||||
("Export Server Config", "Eksportuoti serverio konfigūraciją"),
|
||||
("Import server configuration successfully", "Sėkmingai importuoti serverio konfigūraciją"),
|
||||
("Export server configuration successfully", "Sėkmingai eksportuoti serverio konfigūraciją"),
|
||||
("Invalid server configuration", "Netinkama serverio konfigūracija"),
|
||||
("Clipboard is empty", "Iškarpinė tuščia"),
|
||||
("Stop service", "Sustabdyti paslaugą"),
|
||||
("Change ID", "Keisti ID"),
|
||||
("Your new ID", "Jūsų naujasis ID"),
|
||||
("length %min% to %max%", "ilgis %min% iki %max%"),
|
||||
("starts with a letter", "prasideda raide"),
|
||||
("allowed characters", "leistini simboliai"),
|
||||
("id_change_tip", "Leidžiami tik simboliai a–z, A–Z, 0–9 ir _ (pabraukimas). Pirmoji raidė turi būti a-z, A-Z. Ilgis nuo 6 iki 16."),
|
||||
("Website", "Interneto svetainė"),
|
||||
("About", "Apie"),
|
||||
("Slogan_tip", "Sukurta su siela šiame beprotiškame pasaulyje!"),
|
||||
("Privacy Statement", "Privatumo pareiškimas"),
|
||||
("Mute", "Nutildyti"),
|
||||
("Build Date", "Sukūrimo data"),
|
||||
("Version", "Versija"),
|
||||
("Home", "Namai"),
|
||||
("Audio Input", "Garso įvestis"),
|
||||
("Enhancements", "Patobulinimai"),
|
||||
("Hardware Codec", "Aparatinės įrangos paspartinimas"),
|
||||
("Adaptive Bitrate", "Adaptyvusis pralaidumas"),
|
||||
("ID Server", "ID serveris"),
|
||||
("Relay Server", "Perdavimo serveris"),
|
||||
("API Server", "API serveris"),
|
||||
("invalid_http", "Turi prasidėti http:// arba https://"),
|
||||
("Invalid IP", "Netinkamas IP"),
|
||||
("Invalid format", "Neteisingas formatas"),
|
||||
("server_not_support", "Serveris dar nepalaikomas"),
|
||||
("Not available", "Nepasiekiamas"),
|
||||
("Too frequent", "Per dažnai"),
|
||||
("Cancel", "Atšaukti"),
|
||||
("Skip", "Praleisti"),
|
||||
("Close", "Uždaryti"),
|
||||
("Retry", "Bandykite dar kartą"),
|
||||
("OK", "GERAI"),
|
||||
("Password Required", "Reikalingas slaptažodis"),
|
||||
("Please enter your password", "Prašome įvesti savo slaptažodį"),
|
||||
("Remember password", "Prisiminti slaptažodį"),
|
||||
("Wrong Password", "Neteisingas slaptažodis"),
|
||||
("Do you want to enter again?", "Ar norite įeiti dar kartą?"),
|
||||
("Connection Error", "Ryšio klaida"),
|
||||
("Error", "Klaida"),
|
||||
("Reset by the peer", "Atmetė nuotolinis kompiuteris"),
|
||||
("Connecting...", "Jungiamasi..."),
|
||||
("Connection in progress. Please wait.", "Jungiamasi. Palaukite."),
|
||||
("Please try 1 minute later", "Prašome pabandyti po 1 minutės"),
|
||||
("Login Error", "Prisijungimo klaida"),
|
||||
("Successful", "Sėkmingai"),
|
||||
("Connected, waiting for image...", "Prisijungta, laukiama vaizdo..."),
|
||||
("Name", "Vardas"),
|
||||
("Type", "Tipas"),
|
||||
("Modified", "Pakeista"),
|
||||
("Size", "Dydis"),
|
||||
("Show Hidden Files", "Rodyti paslėptus failus"),
|
||||
("Receive", "Gauti"),
|
||||
("Send", "Siųsti"),
|
||||
("Refresh File", "Atnaujinti failą"),
|
||||
("Local", "Vietinis"),
|
||||
("Remote", "Nuotolinis"),
|
||||
("Remote Computer", "Nuotolinis kompiuteris"),
|
||||
("Local Computer", "Šis kompiuteris"),
|
||||
("Confirm Delete", "Patvirtinti ištrynimą"),
|
||||
("Delete", "Ištrinti"),
|
||||
("Properties", "Ypatybės"),
|
||||
("Multi Select", "Keli pasirinkimas"),
|
||||
("Select All", "Pasirinkti viską"),
|
||||
("Unselect All", "Atšaukti visų pasirinkimą"),
|
||||
("Empty Directory", "Tuščias katalogas"),
|
||||
("Not an empty directory", "Ne tuščias katalogas"),
|
||||
("Are you sure you want to delete this file?", "Ar tikrai norite ištrinti šį failą?"),
|
||||
("Are you sure you want to delete this empty directory?", "Ar tikrai norite ištrinti šį tuščią katalogą?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Ar tikrai norite ištrinti šio katalogo failą?"),
|
||||
("Do this for all conflicts", "Taikyti visiems konfliktams"),
|
||||
("This is irreversible!", "Tai negrįžtama!"),
|
||||
("Deleting", "Ištrinama"),
|
||||
("files", "failai"),
|
||||
("Waiting", "Laukiu"),
|
||||
("Finished", "Baigta"),
|
||||
("Speed", "Greitis"),
|
||||
("Custom Image Quality", "Tinkinta vaizdo kokybė"),
|
||||
("Privacy mode", "Privatumo režimas"),
|
||||
("Block user input", "Blokuoti naudotojo įvestį"),
|
||||
("Unblock user input", "Atblokuoti naudotojo įvestį"),
|
||||
("Adjust Window", "Koreguoti langą"),
|
||||
("Original", "Originalas"),
|
||||
("Shrink", "Susitraukti"),
|
||||
("Stretch", "Ištempti"),
|
||||
("Scrollbar", "Slinkties juosta"),
|
||||
("ScrollAuto", "Automatinis slinkimas"),
|
||||
("Good image quality", "Gera vaizdo kokybė"),
|
||||
("Balanced", "Subalansuotas"),
|
||||
("Optimize reaction time", "Optimizuoti reakcijos laiką"),
|
||||
("Custom", "Tinkintas"),
|
||||
("Show remote cursor", "Rodyti nuotolinį žymeklį"),
|
||||
("Show quality monitor", "Rodyti kokybės monitorių"),
|
||||
("Disable clipboard", "Išjungti mainų sritį"),
|
||||
("Lock after session end", "Užrakinti pasibaigus seansui"),
|
||||
("Insert", "Įdėti"),
|
||||
("Insert Lock", "Įterpti užraktą"),
|
||||
("Refresh", "Atnaujinti"),
|
||||
("ID does not exist", "ID neegzistuoja"),
|
||||
("Failed to connect to rendezvous server", "Nepavyko prisijungti prie susitikimo serverio"),
|
||||
("Please try later", "Prašome pabandyti vėliau"),
|
||||
("Remote desktop is offline", "Nuotolinis darbalaukis neprisijungęs"),
|
||||
("Key mismatch", "Raktų neatitikimas"),
|
||||
("Timeout", "Laikas baigėsi"),
|
||||
("Failed to connect to relay server", "Nepavyko prisijungti prie perdavimo serverio"),
|
||||
("Failed to connect via rendezvous server", "Nepavyko prisijungti per susitikimo serverį"),
|
||||
("Failed to connect via relay server", "Nepavyko prisijungti per perdavimo serverį"),
|
||||
("Failed to make direct connection to remote desktop", "Nepavyko tiesiogiai prisijungti prie nuotolinio darbalaukio"),
|
||||
("Set Password", "Nustatyti slaptažodį"),
|
||||
("OS Password", "OS slaptažodis"),
|
||||
("install_tip", "Kai kuriais atvejais UAC gali priversti RustDesk netinkamai veikti nuotoliniame pagrindiniame kompiuteryje. Norėdami apeiti UAC, spustelėkite toliau esantį mygtuką, kad įdiegtumėte RustDesk į savo kompiuterį."),
|
||||
("Click to upgrade", "Spustelėkite, jei norite atnaujinti"),
|
||||
("Click to download", "Spustelėkite norėdami atsisiųsti"),
|
||||
("Click to update", "Spustelėkite norėdami atnaujinti"),
|
||||
("Configure", "Konfigūruoti"),
|
||||
("config_acc", "Norėdami nuotoliniu būdu valdyti darbalaukį, turite suteikti RustDesk \"prieigos\" leidimus"),
|
||||
("config_screen", "Norėdami nuotoliniu būdu pasiekti darbalaukį, turite suteikti RustDesk leidimus \"ekrano kopija\""),
|
||||
("Installing ...", "Diegiama ..."),
|
||||
("Install", "Diegti"),
|
||||
("Installation", "Įdiegimas"),
|
||||
("Installation Path", "Įdiegimo kelias"),
|
||||
("Create start menu shortcuts", "Sukurti pradžios meniu sparčiuosius klavišus"),
|
||||
("Create desktop icon", "Sukurti darbalaukio piktogramą"),
|
||||
("agreement_tip", "Pradėdami diegimą sutinkate su licencijos sutarties sąlygomis"),
|
||||
("Accept and Install", "Priimti ir įdiegti"),
|
||||
("End-user license agreement", "Galutinio vartotojo licencijos sutartis"),
|
||||
("Generating ...", "Generuojamas..."),
|
||||
("Your installation is lower version.", "Jūsų įdiegta versija senesnė."),
|
||||
("not_close_tcp_tip", "Naudodami tunelį neuždarykite šio lango"),
|
||||
("Listening ...", "Laukimas..."),
|
||||
("Remote Host", "Nuotolinis pagrindinis kompiuteris"),
|
||||
("Remote Port", "Nuotolinis prievadas"),
|
||||
("Action", "Veiksmas"),
|
||||
("Add", "Papildyti"),
|
||||
("Local Port", "Vietinis prievadas"),
|
||||
("Local Address", "Vietinis adresas"),
|
||||
("Change Local Port", "Keisti vietinį prievadą"),
|
||||
("setup_server_tip", "Kad ryšys būtų greitesnis, nustatykite savo serverį"),
|
||||
("Too short, at least 6 characters.", "Per trumpas, mažiausiai 6 simboliai."),
|
||||
("The confirmation is not identical.", "Patvirtinimas nėra tapatus."),
|
||||
("Permissions", "Leidimai"),
|
||||
("Accept", "Priimti"),
|
||||
("Dismiss", "Atmesti"),
|
||||
("Disconnect", "Atjungti"),
|
||||
("Allow using keyboard and mouse", "Leisti naudoti klaviatūrą ir pelę"),
|
||||
("Allow using clipboard", "Leisti naudoti mainų sritį"),
|
||||
("Allow hearing sound", "Leisti girdėti kompiuterio garsą"),
|
||||
("Allow file copy and paste", "Leisti kopijuoti ir įklijuoti failus"),
|
||||
("Connected", "Prisijungta"),
|
||||
("Direct and encrypted connection", "Tiesioginis ir šifruotas ryšys"),
|
||||
("Relayed and encrypted connection", "Perduotas ir šifruotas ryšys"),
|
||||
("Direct and unencrypted connection", "Tiesioginis ir nešifruotas ryšys"),
|
||||
("Relayed and unencrypted connection", "Perduotas ir nešifruotas ryšys"),
|
||||
("Enter Remote ID", "Įveskite nuotolinio ID"),
|
||||
("Enter your password", "Įveskite savo slaptažodį"),
|
||||
("Logging in...", "Prisijungiama..."),
|
||||
("Enable RDP session sharing", "Įgalinti RDP seansų bendrinimą"),
|
||||
("Auto Login", "Automatinis prisijungimas"),
|
||||
("Enable Direct IP Access", "Įgalinti tiesioginę IP prieigą"),
|
||||
("Rename", "Pervardyti"),
|
||||
("Space", "Erdvė"),
|
||||
("Create Desktop Shortcut", "Sukurti nuorodą darbalaukyje"),
|
||||
("Change Path", "Keisti kelią"),
|
||||
("Create Folder", "Sukurti aplanką"),
|
||||
("Please enter the folder name", "Įveskite aplanko pavadinimą"),
|
||||
("Fix it", "Pataisyk tai"),
|
||||
("Warning", "Įspėjimas"),
|
||||
("Login screen using Wayland is not supported", "Prisijungimo ekranas naudojant Wayland nepalaikomas"),
|
||||
("Reboot required", "Reikia paleisti iš naujo"),
|
||||
("Unsupported display server", "Nepalaikomas rodymo serveris"),
|
||||
("x11 expected", "reikalingas x11"),
|
||||
("Port", "Prievadas"),
|
||||
("Settings", "Nustatymai"),
|
||||
("Username", "Vartotojo vardas"),
|
||||
("Invalid port", "Netinkamas prievadas"),
|
||||
("Closed manually by the peer", "Partneris atmetė prašymą prisijungti"),
|
||||
("Enable remote configuration modification", "Įgalinti nuotolinį konfigūracijos modifikavimą"),
|
||||
("Run without install", "Vykdyti be diegimo"),
|
||||
("Connect via relay", "Prisijungti per relę"),
|
||||
("Always connect via relay", "Visada prisijunkite per relę"),
|
||||
("whitelist_tip", "Mane gali pasiekti tik baltajame sąraše esantys IP adresai"),
|
||||
("Login", "Prisijungti"),
|
||||
("Verify", "Patvirtinti"),
|
||||
("Remember me", "Prisimink mane"),
|
||||
("Trust this device", "Pasitikėk šiuo įrenginiu"),
|
||||
("Verification code", "Patvirtinimo kodas"),
|
||||
("verification_tip", "Aptiktas naujas įrenginys ir registruotu el. pašto adresu išsiųstas patvirtinimo kodas. Įveskite jį norėdami tęsti prisijungimą."),
|
||||
("Logout", "Atsijungti"),
|
||||
("Tags", "Žymos"),
|
||||
("Search ID", "Paieškos ID"),
|
||||
("whitelist_sep", "Atskirti kableliu, kabliataškiu, tarpu arba nauja eilute"),
|
||||
("Add ID", "Pridėti ID"),
|
||||
("Add Tag", "Pridėti žymą"),
|
||||
("Unselect all tags", "Atšaukti visų žymų pasirinkimą"),
|
||||
("Network error", "Tinklo klaida"),
|
||||
("Username missed", "Prarastas vartotojo vardas"),
|
||||
("Password missed", "Slaptažodis praleistas"),
|
||||
("Wrong credentials", "Klaidingi kredencialai"),
|
||||
("Edit Tag", "Redaguoti žymą"),
|
||||
("Unremember Password", "Nebeprisiminti slaptažodžio"),
|
||||
("Favorites", "Parankiniai"),
|
||||
("Add to Favorites", "Įtraukti į parankinius"),
|
||||
("Remove from Favorites", "Pašalinti iš parankinių"),
|
||||
("Empty", "Tuščia"),
|
||||
("Invalid folder name", "Neteisingas aplanko pavadinimas"),
|
||||
("Socks5 Proxy", "Socks5 Proxy"),
|
||||
("Hostname", "Pagrindinio kompiuterio pavadinimas"),
|
||||
("Discovered", "Aptikta tinkle"),
|
||||
("install_daemon_tip", "Norėdami, kad RustDesk startuotų automatiškai, turite ją įdiegti"),
|
||||
("Remote ID", "Nuotolinis ID"),
|
||||
("Paste", "Įklijuoti"),
|
||||
("Paste here?", "Įklijuoti čia?"),
|
||||
("Are you sure to close the connection?", "Ar tikrai norite atsijungti?"),
|
||||
("Download new version", "Atsisiųsti naują versiją"),
|
||||
("Touch mode", "Palietimo režimas"),
|
||||
("Mouse mode", "Pelės režimas"),
|
||||
("One-Finger Tap", "Palietimas vienu pirštu"),
|
||||
("Left Mouse", "Kairysis pelės kl."),
|
||||
("One-Long Tap", "Vienas palietimas"),
|
||||
("Two-Finger Tap", "Palietimas dviem pirštais"),
|
||||
("Right Mouse", "Dešinysis pelės kl."),
|
||||
("One-Finger Move", "Vieno piršto judesys"),
|
||||
("Double Tap & Move", "Dukart palieskite ir perkelkite"),
|
||||
("Mouse Drag", "Pelės vilkimas"),
|
||||
("Three-Finger vertically", "Trys pirštai vertikaliai"),
|
||||
("Mouse Wheel", "Pelės ratukas"),
|
||||
("Two-Finger Move", "Dviejų pirštų judesys"),
|
||||
("Canvas Move", "Drobės perkėlimas"),
|
||||
("Pinch to Zoom", "Suimkite, kad padidintumėte"),
|
||||
("Canvas Zoom", "Drobės mastelis"),
|
||||
("Reset canvas", "Atstatyti drobę"),
|
||||
("No permission of file transfer", "Nėra leidimo perkelti failus"),
|
||||
("Note", "Pastaba"),
|
||||
("Connection", "Ryšys"),
|
||||
("Share Screen", "Bendrinti ekraną"),
|
||||
("Chat", "Pokalbis"),
|
||||
("Total", "Iš viso"),
|
||||
("items", "elementai"),
|
||||
("Selected", "Pasirinkta"),
|
||||
("Screen Capture", "Ekrano nuotrauka"),
|
||||
("Input Control", "Įvesties valdymas"),
|
||||
("Audio Capture", "Garso fiksavimas"),
|
||||
("File Connection", "Failo ryšys"),
|
||||
("Screen Connection", "Ekrano jungtis"),
|
||||
("Do you accept?", "Ar sutinki?"),
|
||||
("Open System Setting", "Atviros sistemos nustatymas"),
|
||||
("How to get Android input permission?", "Kaip gauti Android įvesties leidimą?"),
|
||||
("android_input_permission_tip1", "Kad nuotolinis įrenginys galėtų valdyti Android įrenginį pele arba liesti, turite leisti RustDesk naudoti \"Prieinamumo\" paslaugą."),
|
||||
("android_input_permission_tip2", "Eikite į kitą sistemos nustatymų puslapį, suraskite \"Įdiegtos paslaugos\" ir įgalinkite \"RustDesk įvestis\" paslaugą."),
|
||||
("android_new_connection_tip", "Gauta nauja užklausa tvarkyti dabartinį įrenginį."),
|
||||
("android_service_will_start_tip", "Įgalinus ekrano fiksavimo paslaugą, kiti įrenginiai gali pateikti užklausą prisijungti prie to įrenginio."),
|
||||
("android_stop_service_tip", "Uždarius paslaugą automatiškai bus uždaryti visi užmegzti ryšiai."),
|
||||
("android_version_audio_tip", "Dabartinė Android versija nepalaiko garso įrašymo, atnaujinkite į Android 10 ar naujesnę versiją."),
|
||||
("android_start_service_tip", "Spustelėkite [Paleisti paslaugą] arba įjunkite [Fiksuoti ekraną], kad paleistumėte ekrano bendrinimo paslaugą."),
|
||||
("android_permission_may_not_change_tip", "Užmegztų ryšių leidimų keisti negalima, reikia prisijungti iš naujo."),
|
||||
("Account", "Paskyra"),
|
||||
("Overwrite", "Perrašyti"),
|
||||
("This file exists, skip or overwrite this file?", "Šis failas egzistuoja, praleisti arba perrašyti šį failą?"),
|
||||
("Quit", "Išeiti"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/"),
|
||||
("Help", "Pagalba"),
|
||||
("Failed", "Nepavyko"),
|
||||
("Succeeded", "Pavyko"),
|
||||
("Someone turns on privacy mode, exit", "Kažkas įjungė privatumo režimą, išeiti"),
|
||||
("Unsupported", "Nepalaikomas"),
|
||||
("Peer denied", "Atšaukė"),
|
||||
("Please install plugins", "Įdiekite papildinius"),
|
||||
("Peer exit", "Nuotolinis mazgas neveikia"),
|
||||
("Failed to turn off", "Nepavyko išjungti"),
|
||||
("Turned off", "Išjungti"),
|
||||
("In privacy mode", "Privatumo režimas"),
|
||||
("Out privacy mode", "Išėjimas iš privatumo režimo"),
|
||||
("Language", "Kalba"),
|
||||
("Keep RustDesk background service", "Palikti RustDesk fonine paslauga"),
|
||||
("Ignore Battery Optimizations", "Ignoruoti akumuliatoriaus optimizavimą"),
|
||||
("android_open_battery_optimizations_tip", "Eikite į kitą nustatymų puslapį"),
|
||||
("Start on Boot", "Pradėti paleidžiant"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Paleiskite ekrano bendrinimo paslaugą įkrovos metu, reikia specialių leidimų"),
|
||||
("Connection not allowed", "Ryšys neleidžiamas"),
|
||||
("Legacy mode", "Senasis režimas"),
|
||||
("Map mode", "Žemėlapio režimas"),
|
||||
("Translate mode", "Vertimo režimas"),
|
||||
("Use permanent password", "Naudoti nuolatinį slaptažodį"),
|
||||
("Use both passwords", "Naudoti abu slaptažodžius"),
|
||||
("Set permanent password", "Nustatyti nuolatinį slaptažodį"),
|
||||
("Enable Remote Restart", "Įgalinti nuotolinį paleidimą iš naujo"),
|
||||
("Allow remote restart", "Leisti nuotolinio kompiuterio paleidimą iš naujo"),
|
||||
("Restart Remote Device", "Paleisti nuotolinį kompiuterį iš naujo"),
|
||||
("Are you sure you want to restart", "Ar tikrai norite paleisti iš naujo?"),
|
||||
("Restarting Remote Device", "Nuotolinio įrenginio paleidimas iš naujo"),
|
||||
("remote_restarting_tip", "Nuotolinis įrenginys paleidžiamas iš naujo. Uždarykite šį pranešimą ir po kurio laiko vėl prisijunkite naudodami nuolatinį slaptažodį."),
|
||||
("Copied", "Nukopijuota"),
|
||||
("Exit Fullscreen", "Išeiti iš pilno ekrano"),
|
||||
("Fullscreen", "Per visą ekraną"),
|
||||
("Mobile Actions", "Veiksmai mobiliesiems"),
|
||||
("Select Monitor", "Pasirinkite monitorių"),
|
||||
("Control Actions", "Valdymo veiksmai"),
|
||||
("Display Settings", "Ekrano nustatymai"),
|
||||
("Ratio", "Santykis"),
|
||||
("Image Quality", "Vaizdo kokybė"),
|
||||
("Scroll Style", "Slinkimo stilius"),
|
||||
("Show Menubar", "Rodyti meniu juostą"),
|
||||
("Hide Menubar", "Slėpti meniu juostą"),
|
||||
("Direct Connection", "Tiesioginis ryšys"),
|
||||
("Relay Connection", "Tarpinė jungtis"),
|
||||
("Secure Connection", "Saugus ryšys"),
|
||||
("Insecure Connection", "Nesaugus ryšys"),
|
||||
("Scale original", "Pakeisti originalų mastelį"),
|
||||
("Scale adaptive", "Pritaikomas mastelis"),
|
||||
("General", "Bendra"),
|
||||
("Security", "Sauga"),
|
||||
("Theme", "Tema"),
|
||||
("Dark Theme", "Tamsioji tema"),
|
||||
("Light Theme", "Šviesi tema"),
|
||||
("Dark", "Tamsi"),
|
||||
("Light", "Šviesi"),
|
||||
("Follow System", "Kaip sistemos"),
|
||||
("Enable hardware codec", "Įgalinti"),
|
||||
("Unlock Security Settings", "Atrakinti saugos nustatymus"),
|
||||
("Enable Audio", "Įgalinti garsą"),
|
||||
("Unlock Network Settings", "Atrakinti tinklo nustatymus"),
|
||||
("Server", "Serveris"),
|
||||
("Direct IP Access", "Tiesioginė IP prieiga"),
|
||||
("Proxy", "Tarpinis serveris"),
|
||||
("Apply", "Taikyti"),
|
||||
("Disconnect all devices?", "Atjungti visus įrenginius?"),
|
||||
("Clear", "Išvalyti"),
|
||||
("Audio Input Device", "Garso įvestis"),
|
||||
("Deny remote access", "Uždrausti nuotolinę prieigą"),
|
||||
("Use IP Whitelisting", "Naudoti patikimą IP sąrašą"),
|
||||
("Network", "Tinklas"),
|
||||
("Enable RDP", "Įgalinti RDP"),
|
||||
("Pin menubar", "Prisegti meniu juostą"),
|
||||
("Unpin menubar", "Atsegti meniu juostą"),
|
||||
("Recording", "Įrašymas"),
|
||||
("Directory", "Katalogas"),
|
||||
("Automatically record incoming sessions", "Automatiškai įrašyti įeinančius seansus"),
|
||||
("Change", "Keisti"),
|
||||
("Start session recording", "Pradėti seanso įrašinėjimą"),
|
||||
("Stop session recording", "Sustabdyti seanso įrašinėjimą"),
|
||||
("Enable Recording Session", "Įgalinti seanso įrašinėjimą"),
|
||||
("Allow recording session", "Leisti seanso įrašinėjimą"),
|
||||
("Enable LAN Discovery", "Įgalinti LAN aptikimą"),
|
||||
("Deny LAN Discovery", "Neleisti LAN aptikimo"),
|
||||
("Write a message", "Rašyti žinutę"),
|
||||
("Prompt", "Užuomina"),
|
||||
("Please wait for confirmation of UAC...", "Palaukite UAC patvirtinimo..."),
|
||||
("elevated_foreground_window_tip", "Dabartinis nuotolinio darbalaukio langas reikalauja didesnių privilegijų, todėl laikinai neįmanoma naudoti pelės ir klaviatūros. Galite paprašyti nuotolinio vartotojo sumažinti dabartinį langą arba spustelėti aukščio mygtuką ryšio valdymo lange. Norint išvengti šios problemos ateityje, rekomenduojama programinę įrangą įdiegti nuotoliniame įrenginyje."),
|
||||
("Disconnected", "Atsijungęs"),
|
||||
("Other", "Kita"),
|
||||
("Confirm before closing multiple tabs", "Patvirtinti prieš uždarant kelis skirtukus"),
|
||||
("Keyboard Settings", "Klaviatūros nustatymai"),
|
||||
("Full Access", "Pilna prieiga"),
|
||||
("Screen Share", "Ekrano bendrinimas"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland reikalauja Ubuntu 21.04 arba naujesnės versijos."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland reikalinga naujesnės Linux Distro versijos. Išbandykite X11 darbalaukį arba pakeiskite OS."),
|
||||
("JumpLink", "Peržiūra"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Prašome pasirinkti ekraną, kurį norite bendrinti (veikiantį kitoje pusėje)."),
|
||||
("Show RustDesk", "Rodyti RustDesk"),
|
||||
("This PC", "Šis kompiuteris"),
|
||||
("or", "arba"),
|
||||
("Continue with", "Tęsti su"),
|
||||
("Elevate", "Pakelti"),
|
||||
("Zoom cursor", "Mastelio keitimo žymeklis"),
|
||||
("Accept sessions via password", "Priimti seansus naudojant slaptažodį"),
|
||||
("Accept sessions via click", "Priimti seansus spustelėjus"),
|
||||
("Accept sessions via both", "Priimti seansus abiem variantais"),
|
||||
("Please wait for the remote side to accept your session request...", "Palaukite, kol nuotolinė pusė priims jūsų seanso užklausą..."),
|
||||
("One-time Password", "Vienkartinis slaptažodis"),
|
||||
("Use one-time password", "Naudoti vienkartinį slaptažodį"),
|
||||
("One-time password length", "Vienkartinio slaptažodžio ilgis"),
|
||||
("Request access to your device", "Prašo leidimo valdyti jūsų įrenginį"),
|
||||
("Hide connection management window", "Slėpti ryšio valdymo langą"),
|
||||
("hide_cm_tip", "Leisti paslėpti didžiąją ir mažąją raidę, jei priimamos slaptažodžio sesijos arba naudojamas nuolatinis slaptažodis"),
|
||||
("wayland_experiment_tip", "Wayland palaikymas yra eksperimentinis, naudokite X11, jei jums reikalingas automatinis prisijungimas."),
|
||||
("Right click to select tabs", "Dešiniuoju pelės mygtuku spustelėkite, kad pasirinktumėte skirtukus"),
|
||||
("Skipped", "Praleisti"),
|
||||
("Add to Address Book", "Pridėti prie adresų knygos"),
|
||||
("Group", "Grupė"),
|
||||
("Search", "Paieška"),
|
||||
("Closed manually by web console", "Uždaryta rankiniu būdu naudojant žiniatinklio konsolę"),
|
||||
("Local keyboard type", "Vietinės klaviatūros tipas"),
|
||||
("Select local keyboard type", "Pasirinkite vietinės klaviatūros tipą"),
|
||||
("software_render_tip", "Jei turite Nvidia vaizdo plokštę ir nuotolinis langas iškart užsidaro prisijungus, gali padėti „Nouveau“ tvarkyklės įdiegimas ir programinės įrangos atvaizdavimo pasirinkimas. Būtina paleisti iš naujo."),
|
||||
("Always use software rendering", "Visada naudoti programinį spartintuvą"),
|
||||
("config_input", "Norėdami valdyti nuotolinį darbalaukį naudodami klaviatūrą, turite suteikti RustDesk leidimus \"Įvesties monitoringas\"."),
|
||||
("config_microphone", "Norėdami kalbėtis su nuotoline puse, turite suteikti RustDesk leidimą \"Įrašyti garsą\"."),
|
||||
("request_elevation_tip", "Taip pat galite prašyti tesių suteikimo, jeigu kas nors yra nuotolinėje pusėje."),
|
||||
("Wait", "Laukti"),
|
||||
("Elevation Error", "Teisių suteikimo klaida"),
|
||||
("Ask the remote user for authentication", "Klauskite nuotolinio vartotojo autentifikavimo"),
|
||||
("Choose this if the remote account is administrator", "Pasirinkite tai, jei nuotolinė paskyra yra administratorius"),
|
||||
("Transmit the username and password of administrator", "Persiųsti administratoriaus vartotojo vardą ir slaptažodį"),
|
||||
("still_click_uac_tip", "Vis tiek reikia, kad nuotolinis vartotojas paleidžiant RustDesk UAC lange paspaustų \"OK\"."),
|
||||
("Request Elevation", "Prašyti teisių"),
|
||||
("wait_accept_uac_tip", "Palaukite, kol nuotolinis vartotojas patvirtins UAC užklausą."),
|
||||
("Elevate successfully", "Teisės suteiktos"),
|
||||
("uppercase", "didžiosios raidės"),
|
||||
("lowercase", "mažosios raidės"),
|
||||
("digit", "skaitmuo"),
|
||||
("special character", "specialusis simbolis"),
|
||||
("length>=8", "ilgis>=8"),
|
||||
("Weak", "Silpnas"),
|
||||
("Medium", "Vidutinis"),
|
||||
("Strong", "Stiprus"),
|
||||
("Switch Sides", "Perjungti puses"),
|
||||
("Please confirm if you want to share your desktop?", "Prašome patvirtinti, jeigu norite bendrinti darbalaukį?"),
|
||||
("Display", "Ekranas"),
|
||||
("Default View Style", "Numatytasis peržiūros stilius"),
|
||||
("Default Scroll Style", "Numatytasis slinkties stilius"),
|
||||
("Default Image Quality", "Numatytoji vaizdo kokybė"),
|
||||
("Default Codec", "Numatytasis kodekas"),
|
||||
("Bitrate", "Sparta"),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "Automatinis"),
|
||||
("Other Default Options", "Kitos numatytosios parinktys"),
|
||||
("Voice call", "Balso skambutis"),
|
||||
("Text chat", "Tekstinis pokalbis"),
|
||||
("Stop voice call", "Sustabdyti balso skambutį"),
|
||||
("relay_hint_tip", "Tiesioginis ryšys gali būti neįmanomas. Tokiu atveju galite pabandyti prisijungti per perdavimo serverį. \nArba, jei norite iš karto naudoti perdavimo serverį, prie ID galite pridėti priesagą \"/r\" arba nuotolinio pagrindinio kompiuterio nustatymuose įgalinti \"Visada prisijungti per relę\"."),
|
||||
("Reconnect", "Prisijungti iš naujo"),
|
||||
("Codec", "Kodekas"),
|
||||
("Resolution", "Rezoliucija"),
|
||||
("No transfers in progress", "Nevyksta jokių perdavimų"),
|
||||
("Set one-time password length", "Nustatyti vienkartinio slaptažodžio ilgį"),
|
||||
("idd_driver_tip", "Įdiekite virtualaus ekrano tvarkyklę (naudojama, kai nėra fizinių ekranų)"),
|
||||
("confirm_idd_driver_tip", "Įjungta virtualaus ekrano tvarkyklės diegimo funkcija. Atminkite, kad bus įdiegtas bandomasis sertifikatas, kad būtų galima pasitikėti tvarkykle. Šis sertifikatas bus naudojamas tik pasitikėjimui Rustdesk tvarkyklėmis patikrinti."),
|
||||
("RDP Settings", "RDP nustatymai"),
|
||||
("Sort by", "Rūšiuoti pagal"),
|
||||
("New Connection", "Naujas ryšys"),
|
||||
("Restore", "Atkurti"),
|
||||
("Minimize", "Sumažinti"),
|
||||
("Maximize", "Padidinti"),
|
||||
("Your Device", "Jūsų įrenginys"),
|
||||
("empty_recent_tip", "Nėra paskutinių seansų!\nLaikas suplanuoti naują."),
|
||||
("empty_favorite_tip", "Dar neturite parankinių nuotolinių seansų."),
|
||||
("empty_lan_tip", "Nuotolinių mazgų nerasta."),
|
||||
("empty_address_book_tip", "Adresų knygelėje nėra nuotolinių kompiuterių."),
|
||||
("eg: admin", "pvz.: administratorius"),
|
||||
("Empty Username", "Tuščias naudotojo vardas"),
|
||||
("Empty Password", "Tuščias slaptažodis"),
|
||||
("Me", "Aš"),
|
||||
("identical_file_tip", "Failas yra identiškas nuotoliniame kompiuteryje esančiam failui."),
|
||||
("show_monitors_tip", "Rodyti monitorius įrankių juostoje"),
|
||||
("View Mode", "Peržiūros režimas"),
|
||||
("login_linux_tip", "Norėdami įjungti X darbalaukio seansą, turite būti prisijungę prie nuotolinės Linux paskyros."),
|
||||
("verify_rustdesk_password_tip", "Įveskite kliento RustDesk slaptažodį"),
|
||||
("remember_account_tip", "Prisiminti šią paskyrą"),
|
||||
("os_account_desk_tip", "Ši paskyra naudojama norint prisijungti prie nuotolinės OS ir įgalinti darbalaukio seansą režimu headless"),
|
||||
("OS Account", "OS paskyra"),
|
||||
("another_user_login_title_tip", "Kitas vartotojas jau yra prisijungęs"),
|
||||
("another_user_login_text_tip", "Atjungti"),
|
||||
("xorg_not_found_title_tip", "Xorg nerastas"),
|
||||
("xorg_not_found_text_tip", "Prašom įdiegti Xorg"),
|
||||
("no_desktop_title_tip", "Nėra pasiekiamų nuotolinių darbalaukių"),
|
||||
("no_desktop_text_tip", "Prašom įdiegti GNOME Desktop"),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
@@ -37,10 +37,10 @@ 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", ""),
|
||||
("Your new ID", "Twój nowy ID"),
|
||||
("length %min% to %max%", "o długości od %min% do %max%"),
|
||||
("starts with a letter", "rozpoczyna się literą"),
|
||||
("allowed characters", "dozwolone znaki"),
|
||||
("id_change_tip", "Nowy ID może być złożony z małych i dużych liter a-zA-z, cyfry 0-9 oraz _ (podkreślenie). Pierwszym znakiem powinna być litera a-zA-Z, a całe ID powinno składać się z 6 do 16 znaków."),
|
||||
("Website", "Strona internetowa"),
|
||||
("About", "O aplikacji"),
|
||||
@@ -213,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Closed manually by the peer", "Połączenie zakończone ręcznie przez peer"),
|
||||
("Enable remote configuration modification", "Włącz zdalną modyfikację konfiguracji"),
|
||||
("Run without install", "Uruchom bez instalacji"),
|
||||
("Connect via relay", ""),
|
||||
("Connect via relay", "Połącz bezpośrednio"),
|
||||
("Always connect via relay", "Zawsze łącz pośrednio"),
|
||||
("whitelist_tip", "Zezwalaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
|
||||
("Login", "Zaloguj"),
|
||||
@@ -240,7 +240,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Remove from Favorites", "Usuń z ulubionych"),
|
||||
("Empty", "Pusto"),
|
||||
("Invalid folder name", "Nieprawidłowa nazwa folderu"),
|
||||
("Socks5 Proxy", "Socks5 Proxy"),
|
||||
("Socks5 Proxy", "Proxy Socks5"),
|
||||
("Hostname", "Nazwa hosta"),
|
||||
("Discovered", "Wykryte"),
|
||||
("install_daemon_tip", "Podpowiedź instalacji daemona"),
|
||||
@@ -288,8 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
("android_permission_may_not_change_tip", ""),
|
||||
("android_start_service_tip", "Kliknij [Uruchom serwis] lub włącz uprawnienia [Zrzuty ekranu], aby uruchomić usługę udostępniania ekranu."),
|
||||
("android_permission_may_not_change_tip", "Uprawnienia do nawiązanych połączeń nie mogą być zmieniane automatycznie, dopiero po ponownym połączeniu."),
|
||||
("Account", "Konto"),
|
||||
("Overwrite", "Nadpisz"),
|
||||
("This file exists, skip or overwrite this file?", "Ten plik istnieje, pominąć czy nadpisać ten plik?"),
|
||||
@@ -348,7 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Security", "Zabezpieczenia"),
|
||||
("Theme", "Motyw"),
|
||||
("Dark Theme", "Ciemny motyw"),
|
||||
("Light Theme", ""),
|
||||
("Light Theme", "Jasny motyw"),
|
||||
("Dark", "Ciemny"),
|
||||
("Light", "Jasny"),
|
||||
("Follow System", "Zgodny z systemem"),
|
||||
@@ -408,7 +408,7 @@ 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", "Pozwalaj na ukrycie tylko gdy akceptujesz sesje za pośrednictwem hasła i używasz hasła permanentnego"),
|
||||
("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"),
|
||||
@@ -421,7 +421,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
("config_microphone", "Aby umożliwić zdalne rozmowy należy przyznać RuskDesk uprawnienia do \"Nagrań audio\"."),
|
||||
("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ń"),
|
||||
@@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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"),
|
||||
("Default Codec", "Domyślny kodek"),
|
||||
("Bitrate", "Bitrate"),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "Auto"),
|
||||
@@ -459,26 +459,44 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Codec", "Kodek"),
|
||||
("Resolution", "Rozdzielczość"),
|
||||
("No transfers in progress", "Brak transferów w toku"),
|
||||
("Set one-time password length", ""),
|
||||
("idd_driver_tip", ""),
|
||||
("confirm_idd_driver_tip", ""),
|
||||
("RDP Settings", ""),
|
||||
("Sort by", ""),
|
||||
("New Connection", ""),
|
||||
("Restore", ""),
|
||||
("Minimize", ""),
|
||||
("Maximize", ""),
|
||||
("Your Device", ""),
|
||||
("empty_recent_tip", ""),
|
||||
("empty_favorite_tip", ""),
|
||||
("empty_lan_tip", ""),
|
||||
("empty_address_book_tip", ""),
|
||||
("eg: admin", ""),
|
||||
("Empty Username", ""),
|
||||
("Empty Password", ""),
|
||||
("Me", ""),
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("Set one-time password length", "Ustaw długość jednorazowego hasła"),
|
||||
("idd_driver_tip", "Zainstaluj sterownik wirtualnego wyświetlacza, który jest używany, gdy nie masz fizycznych monitorów."),
|
||||
("confirm_idd_driver_tip", "Opcja instalacji sterownika wirtualnego wyświetlacza jest zaznaczona. Pamiętaj, że zostanie zainstalowany testowy certyfikat, aby zaufać wirtualnemu sterownikowi. Ten certyfikat będzie używany tylko do weryfikacji sterowników RustDesk."),
|
||||
("RDP Settings", "Ustawienia RDP"),
|
||||
("Sort by", "Sortuj wg"),
|
||||
("New Connection", "Nowe połączenie"),
|
||||
("Restore", "Przywróć"),
|
||||
("Minimize", "Minimalizuj"),
|
||||
("Maximize", "Maksymalizuj"),
|
||||
("Your Device", "Twoje urządzenie"),
|
||||
("empty_recent_tip", "Ups, żadnych nowych sesji!\nCzas zaplanować nową."),
|
||||
("empty_favorite_tip", "Brak ulubionych?\nZnajdźmy kogoś, z kim możesz się połączyć i dodaj Go do ulubionych!"),
|
||||
("empty_lan_tip", "Ojej, wygląda na to, że nie odkryliśmy żadnych urządzeń z RustDesk w Twojej sieci."),
|
||||
("empty_address_book_tip", "Ojej, wygląda na to, że nie ma żadnych wpisów w Twojej książce adresowej."),
|
||||
("eg: admin", "np. admin"),
|
||||
("Empty Username", "Pusty użytkownik"),
|
||||
("Empty Password", "Puste hasło"),
|
||||
("Me", "Ja"),
|
||||
("identical_file_tip", "Ten plik jest identyczny z plikiem na drugim komputerze."),
|
||||
("show_monitors_tip", "Pokaż monitory w zasobniku"),
|
||||
("View Mode", "Tryb widoku"),
|
||||
("login_linux_tip", "Zaloguj do zdalnego konta Linux"),
|
||||
("verify_rustdesk_password_tip", "Weryfikuj hasło RustDesk"),
|
||||
("remember_account_tip", "Zapamiętaj to konto"),
|
||||
("os_account_desk_tip", "To konto jest używane do logowania do zdalnych systemów i włącza bezobsługowe sesje pulpitu"),
|
||||
("OS Account", "Konto systemowe"),
|
||||
("another_user_login_title_tip", "Inny użytkownik jest już zalogowany"),
|
||||
("another_user_login_text_tip", "Rozłącz"),
|
||||
("xorg_not_found_title_tip", "Nie znaleziono Xorg"),
|
||||
("xorg_not_found_text_tip", "Proszę zainstalować Xorg"),
|
||||
("no_desktop_title_tip", "Żaden pulpit nie jest dostępny"),
|
||||
("no_desktop_text_tip", "Proszę zainstalować pulpit GNOME"),
|
||||
("No need to elevate", "Podniesienie uprawnień nie jest wymagane"),
|
||||
("System Sound", "Dźwięk Systemowy"),
|
||||
("Default", "Domyślne"),
|
||||
("New RDP", "Nowe RDP"),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -460,7 +460,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resolution", "Разрешение"),
|
||||
("No transfers in progress", "Передача не осуществляется"),
|
||||
("Set one-time password length", "Установить длину одноразового пароля"),
|
||||
("idd_driver_tip", "Установите драйвер виртуального дисплея, который используется при отсутствии физических дисплеев."),
|
||||
("idd_driver_tip", "Установить драйвер виртуального дисплея (используется при отсутствии физических дисплеев)"),
|
||||
("confirm_idd_driver_tip", "Включена функция установки драйвера виртуального дисплея. Обратите внимание, что для доверия к драйверу будет установлен тестовый сертификат. Этот сертификат будет использоваться только для подтверждения доверия драйверам Rustdesk."),
|
||||
("RDP Settings", "Настройки RDP"),
|
||||
("Sort by", "Сортировка"),
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", "Файл идентичен файлу на удалённом узле."),
|
||||
("show_monitors_tip", "Показывать мониторы на панели инструментов"),
|
||||
("View Mode", "Режим просмотра"),
|
||||
("login_linux_tip", "Чтобы включить сеанс рабочего стола X, необходимо войти в удалённый аккаунт Linux."),
|
||||
("verify_rustdesk_password_tip", "Подтвердить пароль RustDesk"),
|
||||
("remember_account_tip", "Запомнить этот аккаунт"),
|
||||
("os_account_desk_tip", "Этот аккаунт используется для входа в удалённую ОС и включения сеанса рабочего стола в режиме headless"),
|
||||
("OS Account", "Аккаунт ОС"),
|
||||
("another_user_login_title_tip", "Другой пользователь уже вошёл в систему"),
|
||||
("another_user_login_text_tip", "Отключить"),
|
||||
("xorg_not_found_title_tip", "Xorg не найден"),
|
||||
("xorg_not_found_text_tip", "Установите Xorg"),
|
||||
("no_desktop_title_tip", "Нет доступных рабочих столов"),
|
||||
("no_desktop_text_tip", "Установите GNOME Desktop"),
|
||||
("No need to elevate", "Повышение прав не требуется"),
|
||||
("System Sound", "Системный звук"),
|
||||
("Default", "По умолчанию"),
|
||||
("New RDP", "Новый RDP"),
|
||||
("Fingerprint", "Отпечаток"),
|
||||
("Copy Fingerprint", "Копировать отпечаток"),
|
||||
("no fingerprints", "отпечатки отсутствуют"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
438
src/lang/tw.rs
438
src/lang/tw.rs
@@ -7,37 +7,37 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Password", "密碼"),
|
||||
("Ready", "就緒"),
|
||||
("Established", "已建立"),
|
||||
("connecting_status", "正在連接至 RustDesk 網路..."),
|
||||
("connecting_status", "正在連線到 RustDesk 網路 ..."),
|
||||
("Enable Service", "啟用服務"),
|
||||
("Start Service", "啟動服務"),
|
||||
("Service is running", "服務正在運行"),
|
||||
("Service is running", "服務正在執行"),
|
||||
("Service is not running", "服務尚未執行"),
|
||||
("not_ready_status", "尚未就緒。請檢查您的網路連線"),
|
||||
("not_ready_status", "尚未就緒,請檢查您的網路連線。"),
|
||||
("Control Remote Desktop", "控制遠端桌面"),
|
||||
("Transfer File", "傳輸檔案"),
|
||||
("Connect", "連接"),
|
||||
("Connect", "連線"),
|
||||
("Recent Sessions", "近期的工作階段"),
|
||||
("Address Book", "通訊錄"),
|
||||
("Confirmation", "確認"),
|
||||
("TCP Tunneling", "TCP 通道"),
|
||||
("Remove", "移除"),
|
||||
("Refresh random password", "重新產生隨機密碼"),
|
||||
("Set your own password", "自行設置密碼"),
|
||||
("Enable Keyboard/Mouse", "啟用鍵盤/滑鼠"),
|
||||
("Set your own password", "自行設定密碼"),
|
||||
("Enable Keyboard/Mouse", "啟用鍵盤和滑鼠"),
|
||||
("Enable Clipboard", "啟用剪貼簿"),
|
||||
("Enable File Transfer", "啟用檔案傳輸"),
|
||||
("Enable TCP Tunneling", "啟用 TCP 通道"),
|
||||
("IP Whitelisting", "IP 白名單"),
|
||||
("ID/Relay Server", "ID/轉送伺服器"),
|
||||
("ID/Relay Server", "ID / 轉送伺服器"),
|
||||
("Import Server Config", "匯入伺服器設定"),
|
||||
("Export Server Config", "導出服務器配置"),
|
||||
("Export Server Config", "匯出伺服器設定"),
|
||||
("Import server configuration successfully", "匯入伺服器設定成功"),
|
||||
("Export server configuration successfully", "導出服務器配置信息成功"),
|
||||
("Export server configuration successfully", "匯出伺服器設定成功"),
|
||||
("Invalid server configuration", "無效的伺服器設定"),
|
||||
("Clipboard is empty", "剪貼簿是空的"),
|
||||
("Stop service", "停止服務"),
|
||||
("Change ID", "更改 ID"),
|
||||
("Your new ID", "你的新 ID"),
|
||||
("Your new ID", "您的新 ID"),
|
||||
("length %min% to %max%", "長度在 %min% 與 %max% 之間"),
|
||||
("starts with a letter", "以字母開頭"),
|
||||
("allowed characters", "使用允許的字元"),
|
||||
@@ -45,22 +45,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Website", "網站"),
|
||||
("About", "關於"),
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", "隱私聲明"),
|
||||
("Privacy Statement", "隱私權聲明"),
|
||||
("Mute", "靜音"),
|
||||
("Build Date", "建構日期"),
|
||||
("Build Date", "構建日期"),
|
||||
("Version", "版本"),
|
||||
("Home", "主頁"),
|
||||
("Home", "首頁"),
|
||||
("Audio Input", "音訊輸入"),
|
||||
("Enhancements", "增強功能"),
|
||||
("Hardware Codec", "硬件編解碼"),
|
||||
("Adaptive Bitrate", "自適應碼率"),
|
||||
("Hardware Codec", "硬體編解碼器"),
|
||||
("Adaptive Bitrate", "自適應位元速率"),
|
||||
("ID Server", "ID 伺服器"),
|
||||
("Relay Server", "轉送伺服器"),
|
||||
("API Server", "API 伺服器"),
|
||||
("invalid_http", "開頭必須為 http:// 或 https://"),
|
||||
("Invalid IP", "IP 無效"),
|
||||
("Invalid format", "格式無效"),
|
||||
("server_not_support", "服務器暫不支持"),
|
||||
("server_not_support", "伺服器暫不支持"),
|
||||
("Not available", "無法使用"),
|
||||
("Too frequent", "修改過於頻繁,請稍後再試。"),
|
||||
("Cancel", "取消"),
|
||||
@@ -75,13 +75,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you want to enter again?", "您要重新輸入嗎?"),
|
||||
("Connection Error", "連線錯誤"),
|
||||
("Error", "錯誤"),
|
||||
("Reset by the peer", "對方重置了連線"),
|
||||
("Connecting...", "正在連接..."),
|
||||
("Connection in progress. Please wait.", "正在連接,請稍候。"),
|
||||
("Reset by the peer", "對方重設了連線"),
|
||||
("Connecting...", "正在連線 ..."),
|
||||
("Connection in progress. Please wait.", "正在連線,請稍候。"),
|
||||
("Please try 1 minute later", "請於 1 分鐘後再試"),
|
||||
("Login Error", "登入錯誤"),
|
||||
("Successful", "成功"),
|
||||
("Connected, waiting for image...", "已連線,等待畫面傳輸..."),
|
||||
("Connected, waiting for image...", "已連線,等待畫面傳輸 ..."),
|
||||
("Name", "名稱"),
|
||||
("Type", "類型"),
|
||||
("Modified", "修改時間"),
|
||||
@@ -89,7 +89,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show Hidden Files", "顯示隱藏檔案"),
|
||||
("Receive", "接收"),
|
||||
("Send", "傳送"),
|
||||
("Refresh File", "刷新文件"),
|
||||
("Refresh File", "重新整理檔案"),
|
||||
("Local", "本地"),
|
||||
("Remote", "遠端"),
|
||||
("Remote Computer", "遠端電腦"),
|
||||
@@ -100,19 +100,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Multi Select", "多選"),
|
||||
("Select All", "全選"),
|
||||
("Unselect All", "取消全選"),
|
||||
("Empty Directory", "空文件夾"),
|
||||
("Not an empty directory", "不是一個空文件夾"),
|
||||
("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 empty directory?", "您確定要刪除此空資料夾嗎?"),
|
||||
("Are you sure you want to delete the file of this directory?", "您確定要刪除此資料夾中的檔案嗎?"),
|
||||
("Do this for all conflicts", "套用到其他衝突"),
|
||||
("This is irreversible!", "此操作不可逆!"),
|
||||
("Deleting", "正在刪除"),
|
||||
("Deleting", "正在刪除 ..."),
|
||||
("files", "檔案"),
|
||||
("Waiting", "正在等候..."),
|
||||
("Waiting", "正在等候 ..."),
|
||||
("Finished", "已完成"),
|
||||
("Speed", "速度"),
|
||||
("Custom Image Quality", "自訂圖片品質"),
|
||||
("Custom Image Quality", "自訂畫面品質"),
|
||||
("Privacy mode", "隱私模式"),
|
||||
("Block user input", "封鎖使用者輸入"),
|
||||
("Unblock user input", "取消封鎖使用者輸入"),
|
||||
@@ -122,10 +122,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stretch", "延展"),
|
||||
("Scrollbar", "滾動條"),
|
||||
("ScrollAuto", "自動滾動"),
|
||||
("Good image quality", "畫面品質良好"),
|
||||
("Good image quality", "最佳化畫面品質"),
|
||||
("Balanced", "平衡"),
|
||||
("Optimize reaction time", "回應速度最佳化"),
|
||||
("Custom", "自定義"),
|
||||
("Optimize reaction time", "最佳化反應時間"),
|
||||
("Custom", "自訂"),
|
||||
("Show remote cursor", "顯示遠端游標"),
|
||||
("Show quality monitor", "顯示質量監測"),
|
||||
("Disable clipboard", "停用剪貼簿"),
|
||||
@@ -134,71 +134,71 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Insert Lock", "鎖定遠端電腦"),
|
||||
("Refresh", "重新載入"),
|
||||
("ID does not exist", "ID 不存在"),
|
||||
("Failed to connect to rendezvous server", "無法連接至 rendezvous 伺服器"),
|
||||
("Failed to connect to rendezvous server", "無法連線到 rendezvous 伺服器"),
|
||||
("Please try later", "請稍候再試"),
|
||||
("Remote desktop is offline", "遠端電腦離線"),
|
||||
("Remote desktop is offline", "遠端桌面已離線"),
|
||||
("Key mismatch", "金鑰不符"),
|
||||
("Timeout", "逾時"),
|
||||
("Failed to connect to relay server", "無法連接至轉送伺服器"),
|
||||
("Failed to connect via rendezvous server", "無法透過 rendezvous 伺服器連接"),
|
||||
("Failed to connect via relay server", "無法透過轉送伺服器連接"),
|
||||
("Failed to make direct connection to remote desktop", "無法直接連線至遠端電腦"),
|
||||
("Set Password", "設置密碼"),
|
||||
("Failed to connect to relay server", "無法連線到轉送伺服器"),
|
||||
("Failed to connect via rendezvous server", "無法透過 rendezvous 伺服器連線"),
|
||||
("Failed to connect via relay server", "無法透過轉送伺服器連線"),
|
||||
("Failed to make direct connection to remote desktop", "無法直接連線到遠端桌面"),
|
||||
("Set Password", "設定密碼"),
|
||||
("OS Password", "作業系統密碼"),
|
||||
("install_tip", "UAC 會導致 RustDesk 在某些情況下無法正常以遠端電腦運作。若要避開 UAC,請點擊下方按鈕將 RustDesk 安裝到系統中。"),
|
||||
("install_tip", "UAC 會導致 RustDesk 在某些情況下無法正常以遠端電腦執行。若要避開 UAC,請點擊下方按鈕將 RustDesk 安裝到系統中。"),
|
||||
("Click to upgrade", "點擊以升級"),
|
||||
("Click to download", "點擊以下載"),
|
||||
("Click to update", "點擊以更新"),
|
||||
("Configure", "設定"),
|
||||
("config_acc", "您需要授予 RustDesk 「協助工具」 權限才能遠端存取電腦。"),
|
||||
("config_screen", "您需要授予 RustDesk 「畫面錄製」 權限才能遠端存取電腦。"),
|
||||
("Installing ...", "正在安裝..."),
|
||||
("config_acc", "您需要授予 RustDesk「協助工具」權限才能存取遠端電腦。"),
|
||||
("config_screen", "您需要授予 RustDesk「畫面錄製」權限才能存取遠端電腦。"),
|
||||
("Installing ...", "正在安裝 ..."),
|
||||
("Install", "安裝"),
|
||||
("Installation", "安裝"),
|
||||
("Installation Path", "安裝路徑"),
|
||||
("Create start menu shortcuts", "建立開始選單捷徑"),
|
||||
("Create desktop icon", "建立桌面圖示"),
|
||||
("Create start menu shortcuts", "新增開始功能表捷徑"),
|
||||
("Create desktop icon", "新增桌面捷徑"),
|
||||
("agreement_tip", "開始安裝即表示接受許可協議"),
|
||||
("Accept and Install", "接受並安裝"),
|
||||
("End-user license agreement", "使用者授權合約"),
|
||||
("Generating ...", "正在產生 ..."),
|
||||
("Your installation is lower version.", "您的安裝版本過舊。"),
|
||||
("Your installation is lower version.", "您安裝的版本過舊。"),
|
||||
("not_close_tcp_tip", "使用通道時請不要關閉此視窗"),
|
||||
("Listening ...", "正在等待通道連接..."),
|
||||
("Listening ...", "正在等待通道連線 ..."),
|
||||
("Remote Host", "遠端主機"),
|
||||
("Remote Port", "遠端連接埠"),
|
||||
("Remote Port", "遠端連線端口"),
|
||||
("Action", "操作"),
|
||||
("Add", "新增"),
|
||||
("Local Port", "本機連接埠"),
|
||||
("Local Port", "本機連線端口"),
|
||||
("Local Address", "本機地址"),
|
||||
("Change Local Port", "修改本機連接埠"),
|
||||
("setup_server_tip", "若您需要更快的連接速度,可以選擇自行建立伺服器"),
|
||||
("Too short, at least 6 characters.", "過短,至少需 6 個字元。"),
|
||||
("Change Local Port", "修改本機連線端口"),
|
||||
("setup_server_tip", "若您需要更快的連線速度,可以選擇自行建立伺服器"),
|
||||
("Too short, at least 6 characters.", "過短,至少需要 6 個字元。"),
|
||||
("The confirmation is not identical.", "兩次輸入不相符"),
|
||||
("Permissions", "權限"),
|
||||
("Accept", "接受"),
|
||||
("Dismiss", "關閉"),
|
||||
("Disconnect", "斷開連線"),
|
||||
("Disconnect", "中斷連線"),
|
||||
("Allow using keyboard and mouse", "允許使用鍵盤和滑鼠"),
|
||||
("Allow using clipboard", "允許使用剪貼簿"),
|
||||
("Allow hearing sound", "允許分享音訊"),
|
||||
("Allow file copy and paste", "允許文件複製和粘貼"),
|
||||
("Connected", "已連接"),
|
||||
("Allow file copy and paste", "允許檔案複製和貼上"),
|
||||
("Connected", "已連線"),
|
||||
("Direct and encrypted connection", "加密直接連線"),
|
||||
("Relayed and encrypted connection", "加密轉送連線"),
|
||||
("Direct and unencrypted connection", "未加密直接連線"),
|
||||
("Relayed and unencrypted connection", "未加密轉送連線"),
|
||||
("Enter Remote ID", "輸入遠端 ID"),
|
||||
("Enter your password", "輸入您的密碼"),
|
||||
("Logging in...", "正在登入..."),
|
||||
("Logging in...", "正在登入 ..."),
|
||||
("Enable RDP session sharing", "啟用 RDP 工作階段共享"),
|
||||
("Auto Login", "自動登入 (鎖定將在設定關閉後套用)"),
|
||||
("Enable Direct IP Access", "允許 IP 直接存取"),
|
||||
("Rename", "重新命名"),
|
||||
("Space", "空白"),
|
||||
("Create Desktop Shortcut", "建立桌面捷徑"),
|
||||
("Create Desktop Shortcut", "新增桌面捷徑"),
|
||||
("Change Path", "更改路徑"),
|
||||
("Create Folder", "建立資料夾"),
|
||||
("Create Folder", "新增資料夾"),
|
||||
("Please enter the folder name", "請輸入資料夾名稱"),
|
||||
("Fix it", "修復"),
|
||||
("Warning", "警告"),
|
||||
@@ -209,9 +209,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Port", "端口"),
|
||||
("Settings", "設定"),
|
||||
("Username", "使用者名稱"),
|
||||
("Invalid port", "連接埠無效"),
|
||||
("Closed manually by the peer", "由對方手動關閉"),
|
||||
("Enable remote configuration modification", "啟用遠端更改設定"),
|
||||
("Invalid port", "連線端口無效"),
|
||||
("Closed manually by the peer", "遠端使用者關閉了工作階段"),
|
||||
("Enable remote configuration modification", "允許遠端使用者更改設定"),
|
||||
("Run without install", "跳過安裝直接執行"),
|
||||
("Connect via relay", "中繼連線"),
|
||||
("Always connect via relay", "一律透過轉送連線"),
|
||||
@@ -219,9 +219,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Login", "登入"),
|
||||
("Verify", "驗證"),
|
||||
("Remember me", "記住我"),
|
||||
("Trust this device", "信任此設備"),
|
||||
("Trust this device", "信任此裝置"),
|
||||
("Verification code", "驗證碼"),
|
||||
("verification_tip", "檢測到新設備登錄,已向註冊郵箱發送了登入驗證碼,請輸入驗證碼繼續登錄"),
|
||||
("verification_tip", "檢測到新裝置登入,已向註冊電子信箱發送了登入驗證碼,請輸入驗證碼以繼續登入"),
|
||||
("Logout", "登出"),
|
||||
("Tags", "標籤"),
|
||||
("Search ID", "搜尋 ID"),
|
||||
@@ -235,18 +235,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "提供的登入資訊有誤"),
|
||||
("Edit Tag", "編輯標籤"),
|
||||
("Unremember Password", "忘掉密碼"),
|
||||
("Favorites", "收藏"),
|
||||
("Add to Favorites", "加入到收藏"),
|
||||
("Remove from Favorites", "從收藏中刪除"),
|
||||
("Favorites", "我的最愛"),
|
||||
("Add to Favorites", "新增到我的最愛"),
|
||||
("Remove from Favorites", "從我的最愛中刪除"),
|
||||
("Empty", "空空如也"),
|
||||
("Invalid folder name", "資料夾名稱無效"),
|
||||
("Socks5 Proxy", "Socks5 代理"),
|
||||
("Hostname", "主機名稱"),
|
||||
("Discovered", "已發現"),
|
||||
("install_daemon_tip", "為了開機啟動,請安裝系統服務。"),
|
||||
("Discovered", "已探索"),
|
||||
("install_daemon_tip", "為了能夠開機時自動啟動,請先安裝系統服務。"),
|
||||
("Remote ID", "遠端 ID"),
|
||||
("Paste", "貼上"),
|
||||
("Paste here?", "貼上到這裡?"),
|
||||
("Paste here?", "貼上到這裡?"),
|
||||
("Are you sure to close the connection?", "您確定要關閉連線嗎?"),
|
||||
("Download new version", "下載新版本"),
|
||||
("Touch mode", "觸控模式"),
|
||||
@@ -265,12 +265,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Canvas Move", "移動畫布"),
|
||||
("Pinch to Zoom", "雙指縮放"),
|
||||
("Canvas Zoom", "縮放畫布"),
|
||||
("Reset canvas", "重置畫布"),
|
||||
("No permission of file transfer", "無文件傳輸權限"),
|
||||
("Reset canvas", "重設畫布"),
|
||||
("No permission of file transfer", "沒有檔案傳輸權限"),
|
||||
("Note", "備註"),
|
||||
("Connection", "連接"),
|
||||
("Share Screen", "共享畫面"),
|
||||
("Chat", "聊天消息"),
|
||||
("Connection", "連線"),
|
||||
("Share Screen", "共享螢幕畫面"),
|
||||
("Chat", "聊天訊息"),
|
||||
("Total", "總計"),
|
||||
("items", "個項目"),
|
||||
("Selected", "已選擇"),
|
||||
@@ -280,26 +280,26 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("File Connection", "檔案連線"),
|
||||
("Screen Connection", "畫面連線"),
|
||||
("Do you accept?", "是否接受?"),
|
||||
("Open System Setting", "打開系統設定"),
|
||||
("Open System Setting", "開啟系統設定"),
|
||||
("How to get Android input permission?", "如何獲取 Android 的輸入權限?"),
|
||||
("android_input_permission_tip1", "取得輸入權限後可以讓遠端裝置通過滑鼠控制此 Android 裝置"),
|
||||
("android_input_permission_tip2", "請在接下來的系統設定頁面中,找到並進入 「已安裝的服務」 頁面,並將 「RustDesk Input」 服務開啟"),
|
||||
("android_new_connection_tip", "收到新的連接控制請求,對方想要控制您目前的設備"),
|
||||
("android_service_will_start_tip", "開啟畫面錄製權限將自動開啟服務,允許其他裝置向此裝置請求建立連接。"),
|
||||
("android_stop_service_tip", "關閉服務將自動關閉所有已建立的連接。"),
|
||||
("android_version_audio_tip", "目前的 Android 版本不支持音訊錄製,請升級至 Android 10 或以上版本。"),
|
||||
("android_start_service_tip", ""),
|
||||
("android_permission_may_not_change_tip", "對於已經建立的連接,權限可能不會立即發生改變,除非重新建立連接。"),
|
||||
("Account", "賬戶"),
|
||||
("Overwrite", "覆寫"),
|
||||
("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要跳過或是覆寫此檔案嗎?"),
|
||||
("android_input_permission_tip1", "取得輸入權限後可以讓遠端裝置透過滑鼠控制此 Android 裝置"),
|
||||
("android_input_permission_tip2", "請在接下來的系統設定頁面中,找到並進入「已安裝的服務」頁面,並將「RustDesk Input」服務開啟"),
|
||||
("android_new_connection_tip", "收到新的連線控制請求,對方想要控制您目前的裝置"),
|
||||
("android_service_will_start_tip", "開啟畫面錄製權限將自動開啟服務,允許其他裝置向此裝置請求建立連線。"),
|
||||
("android_stop_service_tip", "關閉服務將自動關閉所有已建立的連線。"),
|
||||
("android_version_audio_tip", "目前的 Android 版本不支援音訊錄製,請升級到 Android 10 或以上版本。"),
|
||||
("android_start_service_tip", "點擊「啟動服務」或啟用「螢幕錄製」權限,以啟動螢幕共享服務。"),
|
||||
("android_permission_may_not_change_tip", "對於已經建立的連線,權限可能不會立即發生改變,除非重新建立連線。"),
|
||||
("Account", "帳號"),
|
||||
("Overwrite", "取代"),
|
||||
("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要略過或是取代此檔案嗎?"),
|
||||
("Quit", "退出"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/zh-tw/manual/mac/#啟用權限"),
|
||||
("Help", "幫助"),
|
||||
("Failed", "失敗"),
|
||||
("Succeeded", "成功"),
|
||||
("Someone turns on privacy mode, exit", "其他用戶開啟隱私模式,退出"),
|
||||
("Unsupported", "不支持"),
|
||||
("Someone turns on privacy mode, exit", "其他使用者開啟隱私模式,退出"),
|
||||
("Unsupported", "不支援"),
|
||||
("Peer denied", "被控端拒絕"),
|
||||
("Please install plugins", "請安裝插件"),
|
||||
("Peer exit", "被控端退出"),
|
||||
@@ -308,177 +308,195 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("In privacy mode", "開啟隱私模式"),
|
||||
("Out privacy mode", "退出隱私模式"),
|
||||
("Language", "語言"),
|
||||
("Keep RustDesk background service", "保持RustDesk後台服務"),
|
||||
("Ignore Battery Optimizations", "忽略電池優化"),
|
||||
("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的RustDesk應用設置頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "對方不允許連接"),
|
||||
("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", "同時使用兩種密碼"),
|
||||
("Set permanent password", "設定固定密碼"),
|
||||
("Enable Remote Restart", "允許遠程重啓"),
|
||||
("Allow remote restart", "允許遠程重啓"),
|
||||
("Restart Remote Device", "重啓遠程電腦"),
|
||||
("Are you sure you want to restart", "确定要重启"),
|
||||
("Restarting Remote Device", "正在重啓遠程設備"),
|
||||
("remote_restarting_tip", "遠程設備正在重啓,請關閉當前提示框,並在一段時間後使用永久密碼重新連接"),
|
||||
("Enable Remote Restart", "啟用遠端重新啟動"),
|
||||
("Allow remote restart", "允許遠端重新啟動"),
|
||||
("Restart Remote Device", "重新啟動遠端電腦"),
|
||||
("Are you sure you want to restart", "確定要重新啟動"),
|
||||
("Restarting Remote Device", "正在重新啟動遠端裝置"),
|
||||
("remote_restarting_tip", "遠端裝置正在重新啟動,請關閉當前提示框,並在一段時間後使用永久密碼重新連線"),
|
||||
("Copied", "已複製"),
|
||||
("Exit Fullscreen", "退出全屏"),
|
||||
("Fullscreen", "全屏"),
|
||||
("Mobile Actions", "移動端操作"),
|
||||
("Select Monitor", "選擇監視器"),
|
||||
("Exit Fullscreen", "退出全螢幕"),
|
||||
("Fullscreen", "全螢幕"),
|
||||
("Mobile Actions", "手機操作"),
|
||||
("Select Monitor", "選擇顯示器"),
|
||||
("Control Actions", "控制操作"),
|
||||
("Display Settings", "顯示設置"),
|
||||
("Display Settings", "顯示設定"),
|
||||
("Ratio", "比例"),
|
||||
("Image Quality", "畫質"),
|
||||
("Scroll Style", "滾動樣式"),
|
||||
("Show Menubar", "顯示菜單欄"),
|
||||
("Hide Menubar", "隱藏菜單欄"),
|
||||
("Direct Connection", "直接連接"),
|
||||
("Relay Connection", "中繼連接"),
|
||||
("Secure Connection", "安全連接"),
|
||||
("Insecure Connection", "非安全連接"),
|
||||
("Show Menubar", "顯示選單欄"),
|
||||
("Hide Menubar", "隱藏選單欄"),
|
||||
("Direct Connection", "直接連線"),
|
||||
("Relay Connection", "中繼連線"),
|
||||
("Secure Connection", "安全連線"),
|
||||
("Insecure Connection", "非安全連線"),
|
||||
("Scale original", "原始尺寸"),
|
||||
("Scale adaptive", "適應窗口"),
|
||||
("General", "常規"),
|
||||
("Scale adaptive", "適應視窗"),
|
||||
("General", "通用"),
|
||||
("Security", "安全"),
|
||||
("Theme", "主題"),
|
||||
("Dark Theme", "暗黑主題"),
|
||||
("Dark Theme", "黑暗主題"),
|
||||
("Light Theme", "明亮主題"),
|
||||
("Dark", "黑暗"),
|
||||
("Light", "明亮"),
|
||||
("Follow System", "跟隨系統"),
|
||||
("Enable hardware codec", "使用硬件編解碼"),
|
||||
("Unlock Security Settings", "解鎖安全設置"),
|
||||
("Enable Audio", "允許傳輸音頻"),
|
||||
("Unlock Network Settings", "解鎖網絡設置"),
|
||||
("Server", "服務器"),
|
||||
("Direct IP Access", "IP直接訪問"),
|
||||
("Enable hardware codec", "使用硬體編解碼器"),
|
||||
("Unlock Security Settings", "解鎖安全設定"),
|
||||
("Enable Audio", "允許傳輸音訊"),
|
||||
("Unlock Network Settings", "解鎖網路設定"),
|
||||
("Server", "伺服器"),
|
||||
("Direct IP Access", "IP 直接連線"),
|
||||
("Proxy", "代理"),
|
||||
("Apply", "應用"),
|
||||
("Disconnect all devices?", "斷開所有遠程連接?"),
|
||||
("Disconnect all devices?", "中斷所有遠端連線?"),
|
||||
("Clear", "清空"),
|
||||
("Audio Input Device", "音頻輸入設備"),
|
||||
("Deny remote access", "拒絕遠程訪問"),
|
||||
("Use IP Whitelisting", "只允許白名單上的IP訪問"),
|
||||
("Network", "網絡"),
|
||||
("Enable RDP", "允許RDP訪問"),
|
||||
("Pin menubar", "固定菜單欄"),
|
||||
("Unpin menubar", "取消固定菜單欄"),
|
||||
("Recording", "錄屏"),
|
||||
("Directory", "目錄"),
|
||||
("Automatically record incoming sessions", "自動錄製來訪會話"),
|
||||
("Audio Input Device", "音訊輸入裝置"),
|
||||
("Deny remote access", "拒絕遠端存取"),
|
||||
("Use IP Whitelisting", "只允許白名單上的 IP 進行連線"),
|
||||
("Network", "網路"),
|
||||
("Enable RDP", "允許 RDP 訪問"),
|
||||
("Pin menubar", "固定選單欄"),
|
||||
("Unpin menubar", "取消固定選單欄"),
|
||||
("Recording", "錄製"),
|
||||
("Directory", "路徑"),
|
||||
("Automatically record incoming sessions", "自動錄製連入的工作階段"),
|
||||
("Change", "變更"),
|
||||
("Start session recording", "開始錄屏"),
|
||||
("Stop session recording", "結束錄屏"),
|
||||
("Enable Recording Session", "允許錄製會話"),
|
||||
("Allow recording session", "允許錄製會話"),
|
||||
("Enable LAN Discovery", "允許局域網發現"),
|
||||
("Deny LAN Discovery", "拒絕局域網發現"),
|
||||
("Write a message", "輸入聊天消息"),
|
||||
("Start session recording", "開始錄影"),
|
||||
("Stop session recording", "停止錄影"),
|
||||
("Enable Recording Session", "啟用錄製工作階段"),
|
||||
("Allow recording session", "允許錄製工作階段"),
|
||||
("Enable LAN Discovery", "允許區域網路探索"),
|
||||
("Deny LAN Discovery", "拒絕區域網路探索"),
|
||||
("Write a message", "輸入聊天訊息"),
|
||||
("Prompt", "提示"),
|
||||
("Please wait for confirmation of UAC...", "請等待對方確認UAC"),
|
||||
("elevated_foreground_window_tip", "遠端桌面的當前窗口需要更高的權限才能操作, 暫時無法使用鼠標鍵盤, 可以請求對方最小化當前窗口, 或者在連接管理窗口點擊提升。為避免這個問題,建議在遠端設備上安裝本軟件。"),
|
||||
("Disconnected", "會話已結束"),
|
||||
("Please wait for confirmation of UAC...", "請等待對方確認 UAC ..."),
|
||||
("elevated_foreground_window_tip", "目前的遠端桌面視窗需要更高的權限才能繼續操作,暫時無法使用滑鼠、鍵盤,可以請求對方最小化目前視窗,或者在連線管理視窗點擊提升權限。為了避免這個問題,建議在遠端裝置上安裝本軟體。"),
|
||||
("Disconnected", "斷開連線"),
|
||||
("Other", "其他"),
|
||||
("Confirm before closing multiple tabs", "關閉多個分頁前跟我確認"),
|
||||
("Keyboard Settings", "鍵盤設置"),
|
||||
("Confirm before closing multiple tabs", "關閉多個分頁前詢問我"),
|
||||
("Keyboard Settings", "鍵盤設定"),
|
||||
("Full Access", "完全訪問"),
|
||||
("Screen Share", "僅共享屏幕"),
|
||||
("Screen Share", "僅分享螢幕畫面"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland 需要 Ubuntu 21.04 或更高版本。"),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland 需要更高版本的 linux 發行版。 請嘗試 X11 桌面或更改您的操作系統。"),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland 需要更高版本的 Linux 發行版。請嘗試使用 X11 桌面或更改您的作業系統。"),
|
||||
("JumpLink", "查看"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的畫面(在對端操作)。"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的螢幕畫面(在對端操作)。"),
|
||||
("Show RustDesk", "顯示 RustDesk"),
|
||||
("This PC", "此電腦"),
|
||||
("or", "或"),
|
||||
("Continue with", "使用"),
|
||||
("Elevate", "提權"),
|
||||
("Continue with", "繼續"),
|
||||
("Elevate", "提升權限"),
|
||||
("Zoom cursor", "縮放游標"),
|
||||
("Accept sessions via password", "只允許密碼訪問"),
|
||||
("Accept sessions via click", "只允許點擊訪問"),
|
||||
("Accept sessions via both", "允許密碼或點擊訪問"),
|
||||
("Please wait for the remote side to accept your session request...", "請等待對方接受你的連接..."),
|
||||
("Accept sessions via password", "只允許透過輸入密碼進行連線"),
|
||||
("Accept sessions via click", "只允許透過點擊接受進行連線"),
|
||||
("Accept sessions via both", "允許輸入密碼或點擊接受進行連線"),
|
||||
("Please wait for the remote side to accept your session request...", "請等待對方接受您的連線請求 ..."),
|
||||
("One-time Password", "一次性密碼"),
|
||||
("Use one-time password", "使用一次性密碼"),
|
||||
("One-time password length", "一次性密碼長度"),
|
||||
("Request access to your device", "請求訪問你的設備"),
|
||||
("Hide connection management window", "隱藏連接管理窗口"),
|
||||
("hide_cm_tip", "在只允許密碼連接並且只用固定密碼的情況下才允許隱藏"),
|
||||
("wayland_experiment_tip", "Wayland 支持處於實驗階段,如果你需要使用無人值守訪問,請使用 X11。"),
|
||||
("Right click to select tabs", "右鍵選擇選項卡"),
|
||||
("Request access to your device", "請求訪問您的裝置"),
|
||||
("Hide connection management window", "隱藏連線管理視窗"),
|
||||
("hide_cm_tip", "在只允許密碼連線並且只用固定密碼的情況下才允許隱藏"),
|
||||
("wayland_experiment_tip", "Wayland 支援處於實驗階段,如果您需要使用無人值守訪問,請使用 X11。"),
|
||||
("Right click to select tabs", "右鍵選擇分頁"),
|
||||
("Skipped", "已略過"),
|
||||
("Add to Address Book", "添加到地址簿"),
|
||||
("Group", "小組"),
|
||||
("Search", "搜索"),
|
||||
("Closed manually by web console", "被web控制台手動關閉"),
|
||||
("Add to Address Book", "新增到通訊錄"),
|
||||
("Group", "群組"),
|
||||
("Search", "搜尋"),
|
||||
("Closed manually by web console", "被 Web 控制台手動關閉"),
|
||||
("Local keyboard type", "本地鍵盤類型"),
|
||||
("Select local keyboard type", "請選擇本地鍵盤類型"),
|
||||
("software_render_tip", "如果你使用英偉達顯卡, 並且遠程窗口在會話建立後會立刻關閉, 那麼安裝nouveau驅動並且選擇使用軟件渲染可能會有幫助。重啟軟件後生效。"),
|
||||
("Always use software rendering", "使用軟件渲染"),
|
||||
("config_input", "為了能夠通過鍵盤控制遠程桌面, 請給予 RustDesk \"輸入監控\" 權限。"),
|
||||
("config_microphone", "為了支持通過麥克風進行音訊傳輸,請給予 RustDesk \"錄音\"權限。"),
|
||||
("request_elevation_tip", "如果對面有人, 也可以請求提升權限。"),
|
||||
("software_render_tip", "如果您使用 NVIDIA 顯示卡,並且遠端視窗在建立連線後會立刻關閉,那麼請安裝 nouveau 顯示卡驅動程式並且選擇使用軟體渲染可能會有幫助。重新啟動軟體後生效。"),
|
||||
("Always use software rendering", "使用軟體渲染"),
|
||||
("config_input", "為了能夠透過鍵盤控制遠端桌面,請給予 RustDesk \"輸入監控\" 權限。"),
|
||||
("config_microphone", "為了支援透過麥克風進行音訊傳輸,請給予 RustDesk \"錄音\"權限。"),
|
||||
("request_elevation_tip", "如果遠端使用者可以操作電腦,也可以請求提升權限。"),
|
||||
("Wait", "等待"),
|
||||
("Elevation Error", "提權失敗"),
|
||||
("Ask the remote user for authentication", "請求遠端用戶授權"),
|
||||
("Choose this if the remote account is administrator", "當對面電腦是管理員賬號時選擇該選項"),
|
||||
("Transmit the username and password of administrator", "發送管理員賬號的用戶名密碼"),
|
||||
("still_click_uac_tip", "依然需要被控端用戶在UAC窗口點擊確認。"),
|
||||
("Request Elevation", "請求提權"),
|
||||
("wait_accept_uac_tip", "請等待遠端用戶確認UAC對話框。"),
|
||||
("Elevate successfully", "提權成功"),
|
||||
("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"),
|
||||
("special character", "特殊字元"),
|
||||
("length>=8", "長度不能小於 8"),
|
||||
("Weak", "弱"),
|
||||
("Medium", "中"),
|
||||
("Strong", "強"),
|
||||
("Switch Sides", "反轉訪問方向"),
|
||||
("Please confirm if you want to share your desktop?", "請確認是否要讓對方訪問你的桌面?"),
|
||||
("Switch Sides", "反轉存取方向"),
|
||||
("Please confirm if you want to share your desktop?", "請確認是否要讓對方存取您的桌面?"),
|
||||
("Display", "顯示"),
|
||||
("Default View Style", "默認顯示方式"),
|
||||
("Default Scroll Style", "默認滾動方式"),
|
||||
("Default Image Quality", "默認圖像質量"),
|
||||
("Default Codec", "默認編解碼"),
|
||||
("Bitrate", "波特率"),
|
||||
("Default View Style", "預設顯示方式"),
|
||||
("Default Scroll Style", "預設滾動方式"),
|
||||
("Default Image Quality", "預設圖像質量"),
|
||||
("Default Codec", "預設編解碼器"),
|
||||
("Bitrate", "位元速率"),
|
||||
("FPS", "幀率"),
|
||||
("Auto", "自動"),
|
||||
("Other Default Options", "其它默認選項"),
|
||||
("Other Default Options", "其他預設選項"),
|
||||
("Voice call", "語音通話"),
|
||||
("Text chat", "文字聊天"),
|
||||
("Stop voice call", "停止語音聊天"),
|
||||
("relay_hint_tip", "可能無法直連,可以嘗試中繼連接。 \n另外,如果想直接使用中繼連接,可以在ID後面添加/r,或者在卡片選項裡選擇強制走中繼連接。"),
|
||||
("Reconnect", "重連"),
|
||||
("Codec", "編解碼"),
|
||||
("Resolution", "分辨率"),
|
||||
("No transfers in progress", ""),
|
||||
("Set one-time password length", ""),
|
||||
("idd_driver_tip", ""),
|
||||
("confirm_idd_driver_tip", ""),
|
||||
("RDP Settings", ""),
|
||||
("Sort by", ""),
|
||||
("New Connection", ""),
|
||||
("Restore", ""),
|
||||
("Minimize", ""),
|
||||
("Maximize", ""),
|
||||
("Your Device", ""),
|
||||
("empty_recent_tip", ""),
|
||||
("empty_favorite_tip", ""),
|
||||
("empty_lan_tip", ""),
|
||||
("empty_address_book_tip", ""),
|
||||
("eg: admin", ""),
|
||||
("Empty Username", ""),
|
||||
("Empty Password", ""),
|
||||
("Me", ""),
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("Stop voice call", "停止語音通話"),
|
||||
("relay_hint_tip", "可能無法直接連線,可以嘗試中繼連線。\n另外,如果想要直接使用中繼連線,可以在 ID 後面新增/r,或者在卡片選項裡選擇強制走中繼連線。"),
|
||||
("Reconnect", "重新連線"),
|
||||
("Codec", "編解碼器"),
|
||||
("Resolution", "解析度"),
|
||||
("No transfers in progress", "沒有正在進行的傳輸"),
|
||||
("Set one-time password length", "設定一次性密碼長度"),
|
||||
("idd_driver_tip", "安裝虛擬顯示器驅動程式,以便在沒有連接顯示器的情況下啟動虛擬顯示器進行控制。"),
|
||||
("confirm_idd_driver_tip", "安裝虛擬顯示器驅動程式的選項已勾選。請注意,測試證書將被安裝以信任虛擬顯示器驅動。測試證書僅會用於信任 RustDesk 的驅動程式。"),
|
||||
("RDP Settings", "RDP 設定"),
|
||||
("Sort by", "排序方式"),
|
||||
("New Connection", "新連線"),
|
||||
("Restore", "還原"),
|
||||
("Minimize", "最小化"),
|
||||
("Maximize", "最大化"),
|
||||
("Your Device", "您的裝置"),
|
||||
("empty_recent_tip", "空空如也"),
|
||||
("empty_favorite_tip", "空空如也"),
|
||||
("empty_lan_tip", "空空如也"),
|
||||
("empty_address_book_tip", "空空如也"),
|
||||
("eg: admin", "例如:admin"),
|
||||
("Empty Username", "空使用者帳號"),
|
||||
("Empty Password", "空密碼"),
|
||||
("Me", "我"),
|
||||
("identical_file_tip", "此檔案與對方的檔案一致"),
|
||||
("show_monitors_tip", "在工具列中顯示顯示器"),
|
||||
("View Mode", "瀏覽模式"),
|
||||
("login_linux_tip", "需要登入到遠端 Linux 使用者帳戶才能啟用 X 介面"),
|
||||
("verify_rustdesk_password_tip", "驗證 RustDesk 密碼"),
|
||||
("remember_account_tip", "記住此使用者帳戶"),
|
||||
("os_account_desk_tip", "此使用者帳戶將用於登入遠端作業系統並啟用無頭模式的桌面連線"),
|
||||
("OS Account", "作業系統使用者帳戶"),
|
||||
("another_user_login_title_tip", "另一個使用者已經登入"),
|
||||
("another_user_login_text_tip", "斷開連線"),
|
||||
("xorg_not_found_title_tip", "未找到 Xorg"),
|
||||
("xorg_not_found_text_tip", "請安裝 Xorg"),
|
||||
("no_desktop_title_tip", "沒有可用的桌面"),
|
||||
("no_desktop_text_tip", "請安裝 GNOME 桌面"),
|
||||
("No need to elevate", "不需要提升權限"),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", "新的 RDP"),
|
||||
("Fingerprint", "指紋"),
|
||||
("Copy Fingerprint", "複製指紋"),
|
||||
("no fingerprints", "沒有指紋"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
148
src/lang/ua.rs
148
src/lang/ua.rs
@@ -12,7 +12,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Start Service", "Запустити службу"),
|
||||
("Service is running", "Служба працює"),
|
||||
("Service is not running", "Служба не запущена"),
|
||||
("not_ready_status", "Не готово. Будь ласка, перевірте підключення"),
|
||||
("not_ready_status", "Не готово. Будь ласка, перевірте ваше з'єднання"),
|
||||
("Control Remote Desktop", "Керування віддаленою стільницею"),
|
||||
("Transfer File", "Передати файл"),
|
||||
("Connect", "Підключитися"),
|
||||
@@ -32,24 +32,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Import Server Config", "Імпортувати конфігурацію сервера"),
|
||||
("Export Server Config", "Експортувати конфігурацію сервера"),
|
||||
("Import server configuration successfully", "Конфігурацію сервера успішно імпортовано"),
|
||||
("Export server configuration successfully", ""),
|
||||
("Export server configuration successfully", "Конфігурацію сервера успішно експортовано"),
|
||||
("Invalid server configuration", "Недійсна конфігурація сервера"),
|
||||
("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"),
|
||||
("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", "Про RustDesk"),
|
||||
("Slogan_tip", "Створено з душею в цьому хаотичному світі!"),
|
||||
("Privacy Statement", "Декларація про конфіденційність"),
|
||||
("Mute", "Вимкнути звук"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Build Date", "Дата збірки"),
|
||||
("Version", "Версія"),
|
||||
("Home", "Домівка"),
|
||||
("Audio Input", "Аудіовхід"),
|
||||
("Enhancements", "Покращення"),
|
||||
("Hardware Codec", "Апаратний кодек"),
|
||||
@@ -60,7 +60,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("invalid_http", "Повинен починатися з http:// або https://"),
|
||||
("Invalid IP", "Невірна IP-адреса"),
|
||||
("Invalid format", "Невірний формат"),
|
||||
("server_not_support", "Поки не підтримується сервером"),
|
||||
("server_not_support", "Наразі не підтримується сервером"),
|
||||
("Not available", "Недоступно"),
|
||||
("Too frequent", "Занадто часто"),
|
||||
("Cancel", "Скасувати"),
|
||||
@@ -154,16 +154,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("config_screen", "Для віддаленого доступу до стільниці ви повинні надати RustDesk права для \"запису екрану\""),
|
||||
("Installing ...", "Встановлюється..."),
|
||||
("Install", "Встановити"),
|
||||
("Installation", "Установка"),
|
||||
("Installation", "Встановлення"),
|
||||
("Installation Path", "Шлях встановлення"),
|
||||
("Create start menu shortcuts", "Створити ярлики меню \"Пуск\""),
|
||||
("Create desktop icon", "Створити значок на стільниці"),
|
||||
("agreement_tip", "Починаючи установку, ви приймаєте умови ліцензійної угоди"),
|
||||
("agreement_tip", "Починаючи встановлення, ви приймаєте умови ліцензійної угоди"),
|
||||
("Accept and Install", "Прийняти та встановити"),
|
||||
("End-user license agreement", "Ліцензійна угода з кінцевим користувачем"),
|
||||
("Generating ...", "Генерація..."),
|
||||
("Your installation is lower version.", "Ваша установка більш ранньої версії"),
|
||||
("not_close_tcp_tip", "Не закривати це вікно під час використання тунелю"),
|
||||
("Your installation is lower version.", "У вас встановлена більш рання версія"),
|
||||
("not_close_tcp_tip", "Не закривайте це вікно під час використання тунелю"),
|
||||
("Listening ...", "Очікуємо ..."),
|
||||
("Remote Host", "Віддалена машина"),
|
||||
("Remote Port", "Віддалений порт"),
|
||||
@@ -212,16 +212,16 @@ 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", "Запустити без установки"),
|
||||
("Connect via relay", ""),
|
||||
("Run without install", "Запустити без встановлення"),
|
||||
("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", "Виявлено новий пристрій, код підтвердження надіслано на зареєстровану email-адресу, введіть код підтвердження для продовження авторизації."),
|
||||
("Logout", "Вийти"),
|
||||
("Tags", "Ключові слова"),
|
||||
("Search ID", "Пошук за ID"),
|
||||
@@ -311,7 +311,7 @@ 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 on Boot", "Автозапуск"),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Connection not allowed", "Підключення не дозволено"),
|
||||
("Legacy mode", "Застарілий режим"),
|
||||
@@ -326,7 +326,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to restart", "Ви впевнені, що хочете виконати перезапуск?"),
|
||||
("Restarting Remote Device", "Перезавантаження віддаленого пристрою"),
|
||||
("remote_restarting_tip", "Віддалений пристрій перезапускається. Будь ласка, закрийте це повідомлення та через деякий час перепідключіться, використовуючи постійний пароль."),
|
||||
("Copied", ""),
|
||||
("Copied", "Скопійовано"),
|
||||
("Exit Fullscreen", "Вийти з повноекранного режиму"),
|
||||
("Fullscreen", "Повноекранний"),
|
||||
("Mobile Actions", "Мобільні дії"),
|
||||
@@ -348,7 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Security", "Безпека"),
|
||||
("Theme", "Тема"),
|
||||
("Dark Theme", "Темна тема"),
|
||||
("Light Theme", ""),
|
||||
("Light Theme", "Світла тема"),
|
||||
("Dark", "Темна"),
|
||||
("Light", "Світла"),
|
||||
("Follow System", "Як у системі"),
|
||||
@@ -385,7 +385,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevated_foreground_window_tip", "Поточне вікно віддаленої стільниці потребує розширених прав для роботи, тому наразі неможливо використати мишу та клавіатуру. Ви можете запропонувати віддаленому користувачу згорнути поточне вікно чи натиснути кнопку розширення прав у вікні керування з'єднаннями. Для уникнення цієї проблеми, рекомендується встановити програму на віддаленому пристрої"),
|
||||
("Disconnected", "Відключено"),
|
||||
("Other", "Інше"),
|
||||
("Confirm before closing multiple tabs", ""),
|
||||
("Confirm before closing multiple tabs", "Підтверджувати перед закриттям кількох вкладок"),
|
||||
("Keyboard Settings", "Налаштування клавіатури"),
|
||||
("Full Access", "Повний доступ"),
|
||||
("Screen Share", "Демонстрація екрану"),
|
||||
@@ -411,19 +411,19 @@ 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", "Додати IP до Адресної книги"),
|
||||
("Group", "Група"),
|
||||
("Search", "Пошук"),
|
||||
("Closed manually by web console", ""),
|
||||
("Local keyboard type", ""),
|
||||
("Select local keyboard type", ""),
|
||||
("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", ""),
|
||||
("Wait", "Зачекайте"),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
@@ -432,53 +432,71 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("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", ""),
|
||||
("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", ""),
|
||||
("Reconnect", ""),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("Reconnect", "Перепідключитися"),
|
||||
("Codec", "Кодек"),
|
||||
("Resolution", "Роздільна здатність"),
|
||||
("No transfers in progress", ""),
|
||||
("Set one-time password length", ""),
|
||||
("idd_driver_tip", ""),
|
||||
("confirm_idd_driver_tip", ""),
|
||||
("RDP Settings", ""),
|
||||
("Sort by", ""),
|
||||
("New Connection", ""),
|
||||
("Restore", ""),
|
||||
("Minimize", ""),
|
||||
("Maximize", ""),
|
||||
("Your Device", ""),
|
||||
("RDP Settings", "Налаштування RDP"),
|
||||
("Sort by", "Сортувати за"),
|
||||
("New Connection", "Нове з'єднання"),
|
||||
("Restore", "Відновити"),
|
||||
("Minimize", "Згорнути"),
|
||||
("Maximize", "Розгорнути"),
|
||||
("Your Device", "Вам пристрій"),
|
||||
("empty_recent_tip", ""),
|
||||
("empty_favorite_tip", ""),
|
||||
("empty_lan_tip", ""),
|
||||
("empty_address_book_tip", ""),
|
||||
("eg: admin", ""),
|
||||
("eg: admin", "напр. admin"),
|
||||
("Empty Username", ""),
|
||||
("Empty Password", ""),
|
||||
("Me", ""),
|
||||
("Me", "Я"),
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("View Mode", "Режим перегляду"),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", "Користувач ОС"),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -480,5 +480,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
29
src/lib.rs
29
src/lib.rs
@@ -1,7 +1,7 @@
|
||||
mod keyboard;
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
/// cbindgen:ignore
|
||||
pub mod platform;
|
||||
mod keyboard;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use platform::{get_cursor, get_cursor_data, get_cursor_pos, start_os_service};
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
@@ -20,7 +20,12 @@ 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", feature = "flutter")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
feature = "cli",
|
||||
feature = "flutter"
|
||||
)))]
|
||||
pub mod ui;
|
||||
mod version;
|
||||
pub use version::*;
|
||||
@@ -43,6 +48,17 @@ mod license;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod port_forward;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(any(feature = "flutter"))]
|
||||
pub mod api;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(any(feature = "flutter"))]
|
||||
pub mod plugins;
|
||||
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub mod plugin;
|
||||
|
||||
mod tray;
|
||||
|
||||
mod ui_cm_interface;
|
||||
@@ -54,7 +70,8 @@ mod hbbs_http;
|
||||
#[cfg(windows)]
|
||||
pub mod clipboard_file;
|
||||
|
||||
#[cfg(all(windows, feature = "with_rc"))]
|
||||
pub mod rc;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod win_privacy;
|
||||
#[cfg(windows)]
|
||||
pub mod privacy_win_mag;
|
||||
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
pub mod virtual_display_manager;
|
||||
|
||||
@@ -37,6 +37,11 @@ pub fn get_license_from_string(s: &str) -> ResultType<License> {
|
||||
s
|
||||
};
|
||||
if s.contains("host=") {
|
||||
let s = if s.contains("#") {
|
||||
&s[0..s.find("#").unwrap_or(s.len())]
|
||||
} else {
|
||||
s
|
||||
};
|
||||
let strs: Vec<&str> = s.split("host=").collect();
|
||||
if strs.len() == 2 {
|
||||
let strs2: Vec<&str> = strs[1].split(",key=").collect();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use librustdesk::*;
|
||||
|
||||
|
||||
@@ -4,26 +4,13 @@ use hbb_common::ResultType;
|
||||
use license::*;
|
||||
|
||||
fn gen_name(lic: &License) -> ResultType<String> {
|
||||
let tmp = serde_json::to_vec::<License>(lic)?;
|
||||
let tmp = URL_SAFE_NO_PAD.encode(&tmp);
|
||||
let tmp: String = tmp.chars().rev().collect();
|
||||
Ok(tmp)
|
||||
let tmp = URL_SAFE_NO_PAD.encode(&serde_json::to_vec(lic)?);
|
||||
Ok(tmp.chars().rev().collect())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut args = Vec::new();
|
||||
let mut i = 0;
|
||||
for arg in std::env::args() {
|
||||
if i > 0 {
|
||||
args.push(arg);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
let api = if args.len() < 3 {
|
||||
"".to_owned()
|
||||
} else {
|
||||
args[2].clone()
|
||||
};
|
||||
let args: Vec<_> = std::env::args().skip(1).collect();
|
||||
let api = args.get(2).cloned().unwrap_or_default();
|
||||
if args.len() >= 2 {
|
||||
println!(
|
||||
"rustdesk-licensed-{}.exe",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::{CursorData, ResultType};
|
||||
use desktop::Desktop;
|
||||
pub use hbb_common::platform::linux::*;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::anyhow,
|
||||
bail,
|
||||
allow_err, bail,
|
||||
libc::{c_char, c_int, c_long, c_void},
|
||||
log,
|
||||
message_proto::Resolution,
|
||||
regex::{Captures, Regex},
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
@@ -18,7 +18,6 @@ use std::{
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use xrandr_parser::Parser;
|
||||
|
||||
type Xdo = *const c_void;
|
||||
|
||||
@@ -66,6 +65,11 @@ pub struct xcb_xfixes_get_cursor_image {
|
||||
pub pixels: *const c_long,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sleep_millis(millis: u64) {
|
||||
std::thread::sleep(Duration::from_millis(millis));
|
||||
}
|
||||
|
||||
pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
let mut res = None;
|
||||
XDO.with(|xdo| {
|
||||
@@ -192,7 +196,7 @@ fn start_server(user: Option<(String, String)>, server: &mut Option<Child>) {
|
||||
fn stop_server(server: &mut Option<Child>) {
|
||||
if let Some(mut ps) = server.take() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(Duration::from_millis(30));
|
||||
sleep_millis(30);
|
||||
match ps.try_wait() {
|
||||
Ok(Some(_status)) => {}
|
||||
Ok(None) => {
|
||||
@@ -203,82 +207,80 @@ fn stop_server(server: &mut Option<Child>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_x11_env(uid: &str) {
|
||||
log::info!("uid of seat0: {}", uid);
|
||||
let gdm = format!("/run/user/{}/gdm/Xauthority", uid);
|
||||
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 Path::new(&gdm).exists() {
|
||||
gdm
|
||||
} else {
|
||||
let username = get_active_username();
|
||||
if username == "root" {
|
||||
format!("/{}/.Xauthority", username)
|
||||
} else {
|
||||
let tmp = format!("/home/{}/.Xauthority", username);
|
||||
if Path::new(&tmp).exists() {
|
||||
tmp
|
||||
} else {
|
||||
format!("/var/lib/{}/.Xauthority", username)
|
||||
}
|
||||
}
|
||||
};
|
||||
fn set_x11_env(desktop: &Desktop) {
|
||||
log::info!("DISPLAY: {}", desktop.display);
|
||||
log::info!("XAUTHORITY: {}", desktop.xauth);
|
||||
if !desktop.display.is_empty() {
|
||||
std::env::set_var("DISPLAY", &desktop.display);
|
||||
}
|
||||
let mut d = get_env("DISPLAY", uid);
|
||||
if d.is_empty() {
|
||||
d = get_display();
|
||||
if !desktop.xauth.is_empty() {
|
||||
std::env::set_var("XAUTHORITY", &desktop.xauth);
|
||||
}
|
||||
if d.is_empty() {
|
||||
d = ":0".to_owned();
|
||||
}
|
||||
d = d.replace(&whoami::hostname(), "").replace("localhost", "");
|
||||
log::info!("DISPLAY: {}", d);
|
||||
log::info!("XAUTHORITY: {}", auth);
|
||||
std::env::set_var("XAUTHORITY", auth);
|
||||
std::env::set_var("DISPLAY", d);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn stop_rustdesk_servers() {
|
||||
let _ = run_cmds(format!(
|
||||
let _ = run_cmds(&format!(
|
||||
r##"ps -ef | grep -E 'rustdesk +--server' | awk '{{printf("kill -9 %d\n", $2)}}' | bash"##,
|
||||
));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn stop_subprocess() {
|
||||
let _ = run_cmds(&format!(
|
||||
r##"ps -ef | grep '/etc/rustdesk/xorg.conf' | grep -v grep | awk '{{printf("kill -9 %d\n", $2)}}' | bash"##,
|
||||
));
|
||||
let _ = run_cmds(&format!(
|
||||
r##"ps -ef | grep -E 'rustdesk +--cm-no-ui' | grep -v grep | awk '{{printf("kill -9 %d\n", $2)}}' | bash"##,
|
||||
));
|
||||
}
|
||||
|
||||
fn should_start_server(
|
||||
try_x11: bool,
|
||||
uid: &mut String,
|
||||
cur_uid: String,
|
||||
desktop: &Desktop,
|
||||
cm0: &mut bool,
|
||||
last_restart: &mut Instant,
|
||||
server: &mut Option<Child>,
|
||||
) -> bool {
|
||||
let cm = get_cm();
|
||||
let mut start_new = false;
|
||||
if cur_uid != *uid && !cur_uid.is_empty() {
|
||||
*uid = cur_uid;
|
||||
let mut should_kill = false;
|
||||
|
||||
if desktop.is_headless() {
|
||||
if !uid.is_empty() {
|
||||
// From having a monitor to not having a monitor.
|
||||
*uid = "".to_owned();
|
||||
should_kill = true;
|
||||
}
|
||||
} else if desktop.uid != *uid && !desktop.uid.is_empty() {
|
||||
*uid = desktop.uid.clone();
|
||||
if try_x11 {
|
||||
set_x11_env(&uid);
|
||||
set_x11_env(&desktop);
|
||||
}
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(Duration::from_millis(30));
|
||||
*last_restart = Instant::now();
|
||||
}
|
||||
} else if !cm
|
||||
should_kill = true;
|
||||
}
|
||||
|
||||
if !should_kill
|
||||
&& !cm
|
||||
&& ((*cm0 && last_restart.elapsed().as_secs() > 60)
|
||||
|| last_restart.elapsed().as_secs() > 3600)
|
||||
{
|
||||
// restart server if new connections all closed, or every one hour,
|
||||
// as a workaround to resolve "SpotUdp" (dns resolve)
|
||||
// and x server get displays failure issue
|
||||
should_kill = true;
|
||||
log::info!("restart server");
|
||||
}
|
||||
|
||||
if should_kill {
|
||||
if let Some(ps) = server.as_mut() {
|
||||
allow_err!(ps.kill());
|
||||
std::thread::sleep(Duration::from_millis(30));
|
||||
sleep_millis(30);
|
||||
*last_restart = Instant::now();
|
||||
log::info!("restart server");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ps) = server.as_mut() {
|
||||
match ps.try_wait() {
|
||||
Ok(Some(_)) => {
|
||||
@@ -298,15 +300,18 @@ fn should_start_server(
|
||||
// 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));
|
||||
sleep_millis(super::SERVICE_INTERVAL);
|
||||
}
|
||||
|
||||
pub fn start_os_service() {
|
||||
stop_rustdesk_servers();
|
||||
stop_subprocess();
|
||||
start_uinput_service();
|
||||
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
let mut desktop = Desktop::default();
|
||||
let mut sid = "".to_owned();
|
||||
let mut uid = "".to_owned();
|
||||
let mut server: Option<Child> = None;
|
||||
let mut user_server: Option<Child> = None;
|
||||
@@ -319,62 +324,63 @@ pub fn start_os_service() {
|
||||
let mut cm0 = false;
|
||||
let mut last_restart = Instant::now();
|
||||
while running.load(Ordering::SeqCst) {
|
||||
let (cur_uid, cur_user) = get_active_user_id_name();
|
||||
desktop.refresh();
|
||||
|
||||
// for fixing https://github.com/rustdesk/rustdesk/issues/3129 to avoid too much dbus calling,
|
||||
// though duplicate logic here with should_start_server
|
||||
if !(cur_uid != *uid && !cur_uid.is_empty()) {
|
||||
let cm = get_cm();
|
||||
if !(!cm
|
||||
&& ((cm0 && last_restart.elapsed().as_secs() > 60)
|
||||
|| last_restart.elapsed().as_secs() > 3600))
|
||||
{
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let is_wayland = current_is_wayland();
|
||||
|
||||
if cur_user == "root" || !is_wayland {
|
||||
// Duplicate logic here with should_start_server
|
||||
// Login wayland will try to start a headless --server.
|
||||
if desktop.username == "root" || !desktop.is_wayland() || desktop.is_login_wayland() {
|
||||
// try kill subprocess "--server"
|
||||
stop_server(&mut user_server);
|
||||
// try start subprocess "--server"
|
||||
if should_start_server(
|
||||
true,
|
||||
&mut uid,
|
||||
cur_uid,
|
||||
&desktop,
|
||||
&mut cm0,
|
||||
&mut last_restart,
|
||||
&mut server,
|
||||
) {
|
||||
stop_subprocess();
|
||||
force_stop_server();
|
||||
start_server(None, &mut server);
|
||||
}
|
||||
} else if cur_user != "" {
|
||||
if cur_user != "gdm" {
|
||||
// try kill subprocess "--server"
|
||||
stop_server(&mut server);
|
||||
} else if desktop.username != "" {
|
||||
// try kill subprocess "--server"
|
||||
stop_server(&mut server);
|
||||
|
||||
// try start subprocess "--server"
|
||||
if should_start_server(
|
||||
false,
|
||||
&mut uid,
|
||||
cur_uid.clone(),
|
||||
&mut cm0,
|
||||
&mut last_restart,
|
||||
// try start subprocess "--server"
|
||||
if should_start_server(
|
||||
false,
|
||||
&mut uid,
|
||||
&desktop,
|
||||
&mut cm0,
|
||||
&mut last_restart,
|
||||
&mut user_server,
|
||||
) {
|
||||
stop_subprocess();
|
||||
force_stop_server();
|
||||
start_server(
|
||||
Some((desktop.uid.clone(), desktop.username.clone())),
|
||||
&mut user_server,
|
||||
) {
|
||||
force_stop_server();
|
||||
start_server(Some((cur_uid, cur_user)), &mut user_server);
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
force_stop_server();
|
||||
stop_server(&mut user_server);
|
||||
stop_server(&mut server);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
|
||||
let keeps_headless = sid.is_empty() && desktop.is_headless();
|
||||
let keeps_session = sid == desktop.sid;
|
||||
if keeps_headless || keeps_session {
|
||||
// for fixing https://github.com/rustdesk/rustdesk/issues/3129 to avoid too much dbus calling,
|
||||
sleep_millis(500);
|
||||
} else {
|
||||
sleep_millis(super::SERVICE_INTERVAL);
|
||||
}
|
||||
if !desktop.is_headless() {
|
||||
sid = desktop.sid.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ps) = user_server.take().as_mut() {
|
||||
@@ -386,13 +392,15 @@ pub fn start_os_service() {
|
||||
log::info!("Exit");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_active_user_id_name() -> (String, String) {
|
||||
let vec_id_name = get_values_of_seat0([1, 2].to_vec());
|
||||
let vec_id_name = get_values_of_seat0(&[1, 2]);
|
||||
(vec_id_name[0].clone(), vec_id_name[1].clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_active_userid() -> String {
|
||||
get_values_of_seat0([1].to_vec())[0].clone()
|
||||
get_values_of_seat0(&[1])[0].clone()
|
||||
}
|
||||
|
||||
fn get_cm() -> bool {
|
||||
@@ -411,45 +419,6 @@ fn get_cm() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn get_display() -> String {
|
||||
let user = get_active_username();
|
||||
log::debug!("w {}", &user);
|
||||
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();
|
||||
let b = iter.nth(2);
|
||||
if let Some(b) = b {
|
||||
if b.starts_with(":") {
|
||||
return b.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// above not work for gdm user
|
||||
log::debug!("ls -l /tmp/.X11-unix/");
|
||||
let mut last = "".to_owned();
|
||||
if let Ok(output) = Command::new("ls")
|
||||
.args(vec!["-l", "/tmp/.X11-unix/"])
|
||||
.output()
|
||||
{
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
log::debug!(" {}", line);
|
||||
let mut iter = line.split_whitespace();
|
||||
let user_field = iter.nth(2);
|
||||
if let Some(x) = iter.last() {
|
||||
if x.starts_with("X") {
|
||||
last = x.replace("X", ":").to_owned();
|
||||
if user_field == Some(&user) {
|
||||
return last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
last
|
||||
}
|
||||
|
||||
pub fn is_login_wayland() -> bool {
|
||||
if let Ok(contents) = std::fs::read_to_string("/etc/gdm3/custom.conf") {
|
||||
contents.contains("#WaylandEnable=false") || contents.contains("WaylandEnable=true")
|
||||
@@ -460,9 +429,9 @@ pub fn is_login_wayland() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_is_wayland() -> bool {
|
||||
let dtype = get_display_server();
|
||||
return "wayland" == dtype && unsafe { UNMODIFIED };
|
||||
return is_desktop_wayland() && unsafe { UNMODIFIED };
|
||||
}
|
||||
|
||||
// to-do: test the other display manager
|
||||
@@ -475,8 +444,9 @@ fn _get_display_manager() -> String {
|
||||
"gdm3".to_owned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_active_username() -> String {
|
||||
get_values_of_seat0([2].to_vec())[0].clone()
|
||||
get_values_of_seat0(&[2])[0].clone()
|
||||
}
|
||||
|
||||
pub fn get_active_user_home() -> Option<PathBuf> {
|
||||
@@ -490,6 +460,14 @@ pub fn get_active_user_home() -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_env_var(k: &str) -> String {
|
||||
match std::env::var(k) {
|
||||
Ok(v) => v,
|
||||
Err(_e) => "".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
// Headless is enabled, always return true.
|
||||
pub fn is_prelogin() -> bool {
|
||||
let n = get_active_userid().len();
|
||||
n < 4 && n > 1
|
||||
@@ -500,7 +478,7 @@ pub fn is_root() -> bool {
|
||||
}
|
||||
|
||||
fn is_opensuse() -> bool {
|
||||
if let Ok(res) = run_cmds("cat /etc/os-release | grep opensuse".to_owned()) {
|
||||
if let Ok(res) = run_cmds("cat /etc/os-release | grep opensuse") {
|
||||
if !res.is_empty() {
|
||||
return true;
|
||||
}
|
||||
@@ -514,6 +492,9 @@ pub fn run_as_user(arg: Vec<&str>, user: Option<(String, String)>) -> ResultType
|
||||
None => get_active_user_id_name(),
|
||||
};
|
||||
let cmd = std::env::current_exe()?;
|
||||
if uid.is_empty() {
|
||||
bail!("No valid uid");
|
||||
}
|
||||
let xdg = &format!("XDG_RUNTIME_DIR=/run/user/{}", uid) as &str;
|
||||
let mut args = vec![xdg, "-u", &username, cmd.to_str().unwrap_or("")];
|
||||
args.append(&mut arg.clone());
|
||||
@@ -599,21 +580,31 @@ pub fn is_installed() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn get_env_tries(name: &str, uid: &str, n: usize) -> String {
|
||||
pub(super) fn get_env_tries(name: &str, uid: &str, process: &str, n: usize) -> String {
|
||||
for _ in 0..n {
|
||||
let x = get_env(name, uid);
|
||||
let x = get_env(name, uid, process);
|
||||
if !x.is_empty() {
|
||||
return x;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
sleep_millis(300);
|
||||
}
|
||||
"".to_owned()
|
||||
}
|
||||
|
||||
fn get_env(name: &str, uid: &str) -> String {
|
||||
let cmd = format!("ps -u {} -o pid= | xargs -I__ cat /proc/__/environ 2>/dev/null | tr '\\0' '\\n' | grep '^{}=' | tail -1 | sed 's/{}=//g'", uid, name, name);
|
||||
log::debug!("Run: {}", &cmd);
|
||||
if let Ok(x) = run_cmds(cmd) {
|
||||
#[inline]
|
||||
fn get_env(name: &str, uid: &str, process: &str) -> String {
|
||||
let cmd = format!("ps -u {} -f | grep '{}' | grep -v 'grep' | tail -1 | awk '{{print $2}}' | xargs -I__ cat /proc/__/environ 2>/dev/null | tr '\\0' '\\n' | grep '^{}=' | tail -1 | sed 's/{}=//g'", uid, process, name, name);
|
||||
if let Ok(x) = run_cmds(&cmd) {
|
||||
x.trim_end().to_string()
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_env_from_pid(name: &str, pid: &str) -> String {
|
||||
let cmd = format!("cat /proc/{}/environ 2>/dev/null | tr '\\0' '\\n' | grep '^{}=' | tail -1 | sed 's/{}=//g'", pid, name, name);
|
||||
if let Ok(x) = run_cmds(&cmd) {
|
||||
x.trim_end().to_string()
|
||||
} else {
|
||||
"".to_owned()
|
||||
@@ -671,44 +662,99 @@ pub fn get_double_click_time() -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_width_height_from_captures<'t>(caps: &Captures<'t>) -> Option<(i32, i32)> {
|
||||
match (caps.name("width"), caps.name("height")) {
|
||||
(Some(width), Some(height)) => {
|
||||
match (
|
||||
width.as_str().parse::<i32>(),
|
||||
height.as_str().parse::<i32>(),
|
||||
) {
|
||||
(Ok(width), Ok(height)) => {
|
||||
return Some((width, height));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_xrandr_conn_pat(name: &str) -> String {
|
||||
format!(
|
||||
r"{}\s+connected.+?(?P<width>\d+)x(?P<height>\d+)\+(?P<x>\d+)\+(?P<y>\d+).*?\n",
|
||||
name
|
||||
)
|
||||
}
|
||||
|
||||
pub fn resolutions(name: &str) -> Vec<Resolution> {
|
||||
let resolutions_pat = r"(?P<resolutions>(\s*\d+x\d+\s+\d+.*\n)+)";
|
||||
let connected_pat = get_xrandr_conn_pat(name);
|
||||
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);
|
||||
if let Ok(re) = Regex::new(&format!("{}{}", connected_pat, resolutions_pat)) {
|
||||
match run_cmds("xrandr --query | tr -s ' '") {
|
||||
Ok(xrandr_output) => {
|
||||
// There'are different kinds of xrandr output.
|
||||
/*
|
||||
1.
|
||||
Screen 0: minimum 320 x 175, current 1920 x 1080, maximum 1920 x 1080
|
||||
default connected 1920x1080+0+0 0mm x 0mm
|
||||
1920x1080 10.00*
|
||||
1280x720 25.00
|
||||
1680x1050 60.00
|
||||
Virtual2 disconnected (normal left inverted right x axis y axis)
|
||||
Virtual3 disconnected (normal left inverted right x axis y axis)
|
||||
|
||||
XWAYLAND0 connected primary 1920x984+0+0 (normal left inverted right x axis y axis) 0mm x 0mm
|
||||
Virtual1 connected primary 1920x984+0+0 (normal left inverted right x axis y axis) 0mm x 0mm
|
||||
HDMI-0 connected (normal left inverted right x axis y axis)
|
||||
|
||||
rdp0 connected primary 1920x1080+0+0 0mm x 0mm
|
||||
*/
|
||||
if let Some(caps) = re.captures(&xrandr_output) {
|
||||
if let Some(resolutions) = caps.name("resolutions") {
|
||||
let resolution_pat =
|
||||
r"\s*(?P<width>\d+)x(?P<height>\d+)\s+(?P<rates>(\d+\.\d+[* ]*)+)\s*\n";
|
||||
let resolution_re = Regex::new(&format!(r"{}", resolution_pat)).unwrap();
|
||||
for resolution_caps in resolution_re.captures_iter(resolutions.as_str()) {
|
||||
if let Some((width, height)) =
|
||||
get_width_height_from_captures(&resolution_caps)
|
||||
{
|
||||
let resolution = Resolution {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
};
|
||||
if !v.contains(&resolution) {
|
||||
v.push(resolution);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("Failed to run xrandr query, {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
v
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
let xrandr_output = run_cmds("xrandr --query | tr -s ' '")?;
|
||||
let re = Regex::new(&get_xrandr_conn_pat(name))?;
|
||||
if let Some(caps) = re.captures(&xrandr_output) {
|
||||
if let Some((width, height)) = get_width_height_from_captures(&caps) {
|
||||
return Ok(Resolution {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
bail!("Failed to find current resolution for {}", name);
|
||||
}
|
||||
|
||||
pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> {
|
||||
@@ -722,3 +768,190 @@ pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<
|
||||
.spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod desktop {
|
||||
use super::*;
|
||||
|
||||
pub const XFCE4_PANEL: &str = "xfce4-panel";
|
||||
pub const GNOME_SESSION_BINARY: &str = "gnome-session-binary";
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Desktop {
|
||||
pub sid: String,
|
||||
pub username: String,
|
||||
pub uid: String,
|
||||
pub protocal: String,
|
||||
pub display: String,
|
||||
pub xauth: String,
|
||||
pub is_rustdesk_subprocess: bool,
|
||||
}
|
||||
|
||||
impl Desktop {
|
||||
#[inline]
|
||||
pub fn is_wayland(&self) -> bool {
|
||||
self.protocal == DISPLAY_SERVER_WAYLAND
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_login_wayland(&self) -> bool {
|
||||
super::is_gdm_user(&self.username) && self.protocal == DISPLAY_SERVER_WAYLAND
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_headless(&self) -> bool {
|
||||
self.sid.is_empty() || self.is_rustdesk_subprocess
|
||||
}
|
||||
|
||||
fn get_display(&mut self) {
|
||||
self.display = get_env_tries("DISPLAY", &self.uid, GNOME_SESSION_BINARY, 10);
|
||||
if self.display.is_empty() {
|
||||
self.display = get_env_tries("DISPLAY", &self.uid, XFCE4_PANEL, 10);
|
||||
}
|
||||
if self.display.is_empty() {
|
||||
self.display = Self::get_display_by_user(&self.username);
|
||||
}
|
||||
if self.display.is_empty() {
|
||||
self.display = ":0".to_owned();
|
||||
}
|
||||
self.display = self
|
||||
.display
|
||||
.replace(&whoami::hostname(), "")
|
||||
.replace("localhost", "");
|
||||
}
|
||||
|
||||
fn get_xauth_from_xorg(&mut self) {
|
||||
if let Ok(output) = run_cmds(&format!(
|
||||
"ps -u {} -f | grep 'Xorg' | grep -v 'grep'",
|
||||
&self.uid
|
||||
)) {
|
||||
for line in output.lines() {
|
||||
let mut auth_found = false;
|
||||
for v in line.split_whitespace() {
|
||||
if v == "-auth" {
|
||||
auth_found = true;
|
||||
} else if auth_found {
|
||||
if std::path::Path::new(v).is_absolute() {
|
||||
self.xauth = v.to_string();
|
||||
} else {
|
||||
if let Some(pid) = line.split_whitespace().nth(1) {
|
||||
let home_dir = get_env_from_pid("HOME", pid);
|
||||
if home_dir.is_empty() {
|
||||
self.xauth = format!("/home/{}/{}", self.username, v);
|
||||
} else {
|
||||
self.xauth = format!("{}/{}", home_dir, v);
|
||||
}
|
||||
} else {
|
||||
// unreachable!
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_xauth(&mut self) {
|
||||
self.xauth = get_env_tries("XAUTHORITY", &self.uid, GNOME_SESSION_BINARY, 10);
|
||||
if self.xauth.is_empty() {
|
||||
get_env_tries("XAUTHORITY", &self.uid, XFCE4_PANEL, 10);
|
||||
}
|
||||
if self.xauth.is_empty() {
|
||||
self.get_xauth_from_xorg();
|
||||
}
|
||||
|
||||
let gdm = format!("/run/user/{}/gdm/Xauthority", self.uid);
|
||||
if self.xauth.is_empty() {
|
||||
self.xauth = if std::path::Path::new(&gdm).exists() {
|
||||
gdm
|
||||
} else {
|
||||
let username = &self.username;
|
||||
if username == "root" {
|
||||
format!("/{}/.Xauthority", username)
|
||||
} else {
|
||||
let tmp = format!("/home/{}/.Xauthority", username);
|
||||
if std::path::Path::new(&tmp).exists() {
|
||||
tmp
|
||||
} else {
|
||||
format!("/var/lib/{}/.Xauthority", username)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn get_display_by_user(user: &str) -> String {
|
||||
// log::debug!("w {}", &user);
|
||||
if let Ok(output) = std::process::Command::new("w").arg(&user).output() {
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
let mut iter = line.split_whitespace();
|
||||
let b = iter.nth(2);
|
||||
if let Some(b) = b {
|
||||
if b.starts_with(":") {
|
||||
return b.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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")
|
||||
.args(vec!["-l", "/tmp/.X11-unix/"])
|
||||
.output()
|
||||
{
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
let mut iter = line.split_whitespace();
|
||||
let user_field = iter.nth(2);
|
||||
if let Some(x) = iter.last() {
|
||||
if x.starts_with("X") {
|
||||
last = x.replace("X", ":").to_owned();
|
||||
if user_field == Some(&user) {
|
||||
return last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
last
|
||||
}
|
||||
|
||||
fn set_is_subprocess(&mut self) {
|
||||
self.is_rustdesk_subprocess = false;
|
||||
let cmd = "ps -ef | grep 'rustdesk/xorg.conf' | grep -v grep | wc -l";
|
||||
if let Ok(res) = run_cmds(cmd) {
|
||||
if res.trim() != "0" {
|
||||
self.is_rustdesk_subprocess = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
if !self.sid.is_empty() && is_active(&self.sid) {
|
||||
return;
|
||||
}
|
||||
|
||||
let seat0_values = get_values_of_seat0(&[0, 1, 2]);
|
||||
if seat0_values[0].is_empty() {
|
||||
*self = Self::default();
|
||||
self.is_rustdesk_subprocess = false;
|
||||
return;
|
||||
}
|
||||
|
||||
self.sid = seat0_values[0].clone();
|
||||
self.uid = seat0_values[1].clone();
|
||||
self.username = seat0_values[2].clone();
|
||||
self.protocal = get_display_server_of_session(&self.sid).into();
|
||||
if self.is_login_wayland() {
|
||||
self.display = "".to_owned();
|
||||
self.xauth = "".to_owned();
|
||||
self.is_rustdesk_subprocess = false;
|
||||
return;
|
||||
}
|
||||
|
||||
self.get_display();
|
||||
self.get_xauth();
|
||||
self.set_is_subprocess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
733
src/platform/linux_desktop_manager.rs
Normal file
733
src/platform/linux_desktop_manager.rs
Normal file
@@ -0,0 +1,733 @@
|
||||
use super::{linux::*, ResultType};
|
||||
use crate::client::{
|
||||
LOGIN_MSG_DESKTOP_NO_DESKTOP, LOGIN_MSG_DESKTOP_SESSION_ANOTHER_USER,
|
||||
LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LOGIN_MSG_DESKTOP_XORG_NOT_FOUND,
|
||||
LOGIN_MSG_DESKTOP_XSESSION_FAILED,
|
||||
};
|
||||
use hbb_common::{allow_err, bail, log, rand::prelude::*, tokio::time};
|
||||
use pam;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
os::unix::process::CommandExt,
|
||||
path::Path,
|
||||
process::{Child, Command},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::{sync_channel, SyncSender},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use users::{get_user_by_name, os::unix::UserExt, User};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref DESKTOP_RUNNING: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
|
||||
static ref DESKTOP_MANAGER: Arc<Mutex<Option<DesktopManager>>> = Arc::new(Mutex::new(None));
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DesktopManager {
|
||||
seat0_username: String,
|
||||
seat0_display_server: String,
|
||||
child_username: String,
|
||||
child_exit: Arc<AtomicBool>,
|
||||
is_child_running: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
fn check_desktop_manager() {
|
||||
let mut desktop_manager = DESKTOP_MANAGER.lock().unwrap();
|
||||
if let Some(desktop_manager) = &mut (*desktop_manager) {
|
||||
if desktop_manager.is_child_running.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
desktop_manager.child_exit.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
// --server process
|
||||
pub fn start_xdesktop() {
|
||||
std::thread::spawn(|| {
|
||||
*DESKTOP_MANAGER.lock().unwrap() = Some(DesktopManager::new());
|
||||
|
||||
let interval = time::Duration::from_millis(super::SERVICE_INTERVAL);
|
||||
DESKTOP_RUNNING.store(true, Ordering::SeqCst);
|
||||
while DESKTOP_RUNNING.load(Ordering::SeqCst) {
|
||||
check_desktop_manager();
|
||||
std::thread::sleep(interval);
|
||||
}
|
||||
log::info!("xdesktop child thread exit");
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stop_xdesktop() {
|
||||
DESKTOP_RUNNING.store(false, Ordering::SeqCst);
|
||||
*DESKTOP_MANAGER.lock().unwrap() = None;
|
||||
}
|
||||
|
||||
fn detect_headless() -> Option<&'static str> {
|
||||
match run_cmds(&format!("which {}", DesktopManager::get_xorg())) {
|
||||
Ok(output) => {
|
||||
if output.trim().is_empty() {
|
||||
return Some(LOGIN_MSG_DESKTOP_XORG_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Some(LOGIN_MSG_DESKTOP_XORG_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
match run_cmds("ls /usr/share/xsessions/") {
|
||||
Ok(output) => {
|
||||
if output.trim().is_empty() {
|
||||
return Some(LOGIN_MSG_DESKTOP_NO_DESKTOP);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Some(LOGIN_MSG_DESKTOP_NO_DESKTOP);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn try_start_desktop(_username: &str, _passsword: &str) -> String {
|
||||
if _username.is_empty() {
|
||||
let username = get_username();
|
||||
if username.is_empty() {
|
||||
if let Some(msg) = detect_headless() {
|
||||
msg
|
||||
} else {
|
||||
LOGIN_MSG_DESKTOP_SESSION_NOT_READY
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
.to_owned()
|
||||
} else {
|
||||
let username = get_username();
|
||||
if username == _username {
|
||||
// No need to verify password here.
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
if let Some(msg) = detect_headless() {
|
||||
return msg.to_owned();
|
||||
}
|
||||
|
||||
match try_start_x_session(_username, _passsword) {
|
||||
Ok((username, x11_ready)) => {
|
||||
if x11_ready {
|
||||
if _username != username {
|
||||
LOGIN_MSG_DESKTOP_SESSION_ANOTHER_USER.to_owned()
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
} else {
|
||||
LOGIN_MSG_DESKTOP_SESSION_NOT_READY.to_owned()
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to start xsession {}", e);
|
||||
LOGIN_MSG_DESKTOP_XSESSION_FAILED.to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_start_x_session(username: &str, password: &str) -> ResultType<(String, bool)> {
|
||||
let mut desktop_manager = DESKTOP_MANAGER.lock().unwrap();
|
||||
if let Some(desktop_manager) = &mut (*desktop_manager) {
|
||||
if let Some(seat0_username) = desktop_manager.get_supported_display_seat0_username() {
|
||||
return Ok((seat0_username, true));
|
||||
}
|
||||
|
||||
let _ = desktop_manager.try_start_x_session(username, password)?;
|
||||
log::debug!(
|
||||
"try_start_x_session, username: {}, {:?}",
|
||||
&username,
|
||||
&desktop_manager
|
||||
);
|
||||
Ok((
|
||||
desktop_manager.child_username.clone(),
|
||||
desktop_manager.is_running(),
|
||||
))
|
||||
} else {
|
||||
bail!(crate::client::LOGIN_MSG_DESKTOP_NOT_INITED);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_headless() -> bool {
|
||||
DESKTOP_MANAGER
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map_or(false, |manager| {
|
||||
manager.get_supported_display_seat0_username().is_none()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_username() -> String {
|
||||
match &*DESKTOP_MANAGER.lock().unwrap() {
|
||||
Some(manager) => {
|
||||
if let Some(seat0_username) = manager.get_supported_display_seat0_username() {
|
||||
seat0_username
|
||||
} else {
|
||||
if manager.is_running() && !manager.child_username.is_empty() {
|
||||
manager.child_username.clone()
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
None => "".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DesktopManager {
|
||||
fn drop(&mut self) {
|
||||
self.stop_children();
|
||||
}
|
||||
}
|
||||
|
||||
impl DesktopManager {
|
||||
fn fatal_exit() {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
let mut seat0_username = "".to_owned();
|
||||
let mut seat0_display_server = "".to_owned();
|
||||
let seat0_values = get_values_of_seat0(&[0, 2]);
|
||||
if !seat0_values[0].is_empty() {
|
||||
seat0_username = seat0_values[1].clone();
|
||||
seat0_display_server = get_display_server_of_session(&seat0_values[0]);
|
||||
}
|
||||
Self {
|
||||
seat0_username,
|
||||
seat0_display_server,
|
||||
child_username: "".to_owned(),
|
||||
child_exit: Arc::new(AtomicBool::new(true)),
|
||||
is_child_running: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_supported_display_seat0_username(&self) -> Option<String> {
|
||||
if is_gdm_user(&self.seat0_username) && self.seat0_display_server == DISPLAY_SERVER_WAYLAND
|
||||
{
|
||||
None
|
||||
} else if self.seat0_username.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.seat0_username.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_xauth() -> String {
|
||||
let xauth = get_env_var("XAUTHORITY");
|
||||
if xauth.is_empty() {
|
||||
"/tmp/.Xauthority".to_owned()
|
||||
} else {
|
||||
xauth
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_running(&self) -> bool {
|
||||
self.is_child_running.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
fn try_start_x_session(&mut self, username: &str, password: &str) -> ResultType<()> {
|
||||
match get_user_by_name(username) {
|
||||
Some(userinfo) => {
|
||||
let mut client = pam::Client::with_password(pam_get_service_name())?;
|
||||
client
|
||||
.conversation_mut()
|
||||
.set_credentials(username, password);
|
||||
match client.authenticate() {
|
||||
Ok(_) => {
|
||||
if self.is_running() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match self.start_x_session(&userinfo, username, password) {
|
||||
Ok(_) => {
|
||||
log::info!("Succeeded to start x11");
|
||||
self.child_username = username.to_string();
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
bail!("failed to start x session, {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
bail!("failed to check user pass for {}, {}", username, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
bail!("failed to get userinfo of {}", username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The logic mainly fron https://github.com/neutrinolabs/xrdp/blob/34fe9b60ebaea59e8814bbc3ca5383cabaa1b869/sesman/session.c#L334.
|
||||
fn get_avail_display() -> ResultType<u32> {
|
||||
let display_range = 0..51;
|
||||
for i in display_range.clone() {
|
||||
if Self::is_x_server_running(i) {
|
||||
continue;
|
||||
}
|
||||
return Ok(i);
|
||||
}
|
||||
bail!("No avaliable display found in range {:?}", display_range)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_x_server_running(display: u32) -> bool {
|
||||
Path::new(&format!("/tmp/.X11-unix/X{}", display)).exists()
|
||||
|| Path::new(&format!("/tmp/.X{}-lock", display)).exists()
|
||||
}
|
||||
|
||||
fn start_x_session(
|
||||
&mut self,
|
||||
userinfo: &User,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> ResultType<()> {
|
||||
self.stop_children();
|
||||
|
||||
let display_num = Self::get_avail_display()?;
|
||||
// "xServer_ip:display_num.screen_num"
|
||||
|
||||
let uid = userinfo.uid();
|
||||
let gid = userinfo.primary_group_id();
|
||||
let envs = HashMap::from([
|
||||
("SHELL", userinfo.shell().to_string_lossy().to_string()),
|
||||
("PATH", "/sbin:/bin:/usr/bin:/usr/local/bin".to_owned()),
|
||||
("USER", username.to_string()),
|
||||
("UID", userinfo.uid().to_string()),
|
||||
("HOME", userinfo.home_dir().to_string_lossy().to_string()),
|
||||
(
|
||||
"XDG_RUNTIME_DIR",
|
||||
format!("/run/user/{}", userinfo.uid().to_string()),
|
||||
),
|
||||
// ("DISPLAY", self.display.clone()),
|
||||
// ("XAUTHORITY", self.xauth.clone()),
|
||||
// (ENV_DESKTOP_PROTOCAL, XProtocal::X11.to_string()),
|
||||
]);
|
||||
self.child_exit.store(false, Ordering::SeqCst);
|
||||
let is_child_running = self.is_child_running.clone();
|
||||
|
||||
let (tx_res, rx_res) = sync_channel(1);
|
||||
let password = password.to_string();
|
||||
let username = username.to_string();
|
||||
// start x11
|
||||
std::thread::spawn(move || {
|
||||
match Self::start_x_session_thread(
|
||||
tx_res.clone(),
|
||||
is_child_running,
|
||||
uid,
|
||||
gid,
|
||||
display_num,
|
||||
username,
|
||||
password,
|
||||
envs,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
log::error!("Failed to start x session thread");
|
||||
allow_err!(tx_res.send(format!("Failed to start x session thread, {}", e)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// wait x11
|
||||
match rx_res.recv_timeout(Duration::from_millis(10_000)) {
|
||||
Ok(res) => {
|
||||
if res == "" {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!(res)
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
bail!("Failed to recv x11 result {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn display_from_num(num: u32) -> String {
|
||||
format!(":{num}")
|
||||
}
|
||||
|
||||
fn start_x_session_thread(
|
||||
tx_res: SyncSender<String>,
|
||||
is_child_running: Arc<AtomicBool>,
|
||||
uid: u32,
|
||||
gid: u32,
|
||||
display_num: u32,
|
||||
username: String,
|
||||
password: String,
|
||||
envs: HashMap<&str, String>,
|
||||
) -> ResultType<()> {
|
||||
let mut client = pam::Client::with_password(pam_get_service_name())?;
|
||||
client
|
||||
.conversation_mut()
|
||||
.set_credentials(&username, &password);
|
||||
client.authenticate()?;
|
||||
|
||||
client.set_item(pam::PamItemType::TTY, &Self::display_from_num(display_num))?;
|
||||
client.open_session()?;
|
||||
|
||||
// fixme: FreeBSD kernel needs to login here.
|
||||
// see: https://github.com/neutrinolabs/xrdp/blob/a64573b596b5fb07ca3a51590c5308d621f7214e/sesman/session.c#L556
|
||||
|
||||
let (child_xorg, child_wm) = Self::start_x11(uid, gid, username, display_num, &envs)?;
|
||||
is_child_running.store(true, Ordering::SeqCst);
|
||||
|
||||
log::info!("Start xorg and wm done, notify and wait xtop x11");
|
||||
allow_err!(tx_res.send("".to_owned()));
|
||||
|
||||
Self::wait_stop_x11(child_xorg, child_wm);
|
||||
log::info!("Wait x11 stop done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_xorg_exit(child_xorg: &mut Child) -> ResultType<String> {
|
||||
if let Ok(_) = child_xorg.kill() {
|
||||
for _ in 0..3 {
|
||||
match child_xorg.try_wait() {
|
||||
Ok(Some(status)) => return Ok(format!("Xorg exit with {}", status)),
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
// fatal error
|
||||
log::error!("Failed to wait xorg process, {}", e);
|
||||
bail!("Failed to wait xorg process, {}", e)
|
||||
}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(1_000));
|
||||
}
|
||||
log::error!("Failed to wait xorg process, not exit");
|
||||
bail!("Failed to wait xorg process, not exit")
|
||||
} else {
|
||||
Ok("Xorg is already exited".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
fn add_xauth_cookie(
|
||||
file: &str,
|
||||
display: &str,
|
||||
uid: u32,
|
||||
gid: u32,
|
||||
envs: &HashMap<&str, String>,
|
||||
) -> ResultType<()> {
|
||||
let randstr = (0..16)
|
||||
.map(|_| format!("{:02x}", random::<u8>()))
|
||||
.collect::<String>();
|
||||
let output = Command::new("xauth")
|
||||
.uid(uid)
|
||||
.gid(gid)
|
||||
.envs(envs)
|
||||
.args(vec!["-q", "-f", file, "add", display, ".", &randstr])
|
||||
.output()?;
|
||||
// xauth run success, even the following error occurs.
|
||||
// Ok(Output { status: ExitStatus(unix_wait_status(0)), stdout: "", stderr: "xauth: file .Xauthority does not exist\n" })
|
||||
let errmsg = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
if !errmsg.is_empty() {
|
||||
if !errmsg.contains("does not exist") {
|
||||
bail!("Failed to launch xauth, {}", errmsg)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_x_server_running(pid: u32, display_num: u32, max_wait_secs: u64) -> ResultType<()> {
|
||||
let wait_begin = Instant::now();
|
||||
loop {
|
||||
if run_cmds(&format!("ls /proc/{}", pid))?.is_empty() {
|
||||
bail!("X server exit");
|
||||
}
|
||||
|
||||
if Self::is_x_server_running(display_num) {
|
||||
return Ok(());
|
||||
}
|
||||
if wait_begin.elapsed().as_secs() > max_wait_secs {
|
||||
bail!("Failed to wait xserver after {} seconds", max_wait_secs);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
}
|
||||
}
|
||||
|
||||
fn start_x11(
|
||||
uid: u32,
|
||||
gid: u32,
|
||||
username: String,
|
||||
display_num: u32,
|
||||
envs: &HashMap<&str, String>,
|
||||
) -> ResultType<(Child, Child)> {
|
||||
log::debug!("envs of user {}: {:?}", &username, &envs);
|
||||
|
||||
let xauth = Self::get_xauth();
|
||||
let display = Self::display_from_num(display_num);
|
||||
|
||||
Self::add_xauth_cookie(&xauth, &display, uid, gid, &envs)?;
|
||||
|
||||
// Start Xorg
|
||||
let mut child_xorg = Self::start_x_server(&xauth, &display, uid, gid, &envs)?;
|
||||
|
||||
log::info!("xorg started, wait 10 secs to ensuer x server is running");
|
||||
|
||||
let max_wait_secs = 10;
|
||||
// wait x server running
|
||||
if let Err(e) = Self::wait_x_server_running(child_xorg.id(), display_num, max_wait_secs) {
|
||||
match Self::wait_xorg_exit(&mut child_xorg) {
|
||||
Ok(msg) => log::info!("{}", msg),
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
Self::fatal_exit();
|
||||
}
|
||||
}
|
||||
bail!(e)
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"xorg is running, start x window manager with DISPLAY: {}, XAUTHORITY: {}",
|
||||
&display,
|
||||
&xauth
|
||||
);
|
||||
|
||||
std::env::set_var("DISPLAY", &display);
|
||||
std::env::set_var("XAUTHORITY", &xauth);
|
||||
// start window manager (startwm.sh)
|
||||
let child_wm = match Self::start_x_window_manager(uid, gid, &envs) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
match Self::wait_xorg_exit(&mut child_xorg) {
|
||||
Ok(msg) => log::info!("{}", msg),
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
Self::fatal_exit();
|
||||
}
|
||||
}
|
||||
bail!(e)
|
||||
}
|
||||
};
|
||||
log::info!("x window manager is started");
|
||||
|
||||
Ok((child_xorg, child_wm))
|
||||
}
|
||||
|
||||
fn try_wait_x11_child_exit(child_xorg: &mut Child, child_wm: &mut Child) -> bool {
|
||||
match child_xorg.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
log::info!("Xorg exit with {}", status);
|
||||
return true;
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => log::error!("Failed to wait xorg process, {}", e),
|
||||
}
|
||||
|
||||
match child_wm.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
// Logout may result "wm exit with signal: 11 (SIGSEGV) (core dumped)"
|
||||
log::info!("wm exit with {}", status);
|
||||
return true;
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => log::error!("Failed to wait xorg process, {}", e),
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn wait_x11_children_exit(child_xorg: &mut Child, child_wm: &mut Child) {
|
||||
log::debug!("Try kill child process xorg");
|
||||
if let Ok(_) = child_xorg.kill() {
|
||||
let mut exited = false;
|
||||
for _ in 0..2 {
|
||||
match child_xorg.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
log::info!("Xorg exit with {}", status);
|
||||
exited = true;
|
||||
break;
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
log::error!("Failed to wait xorg process, {}", e);
|
||||
Self::fatal_exit();
|
||||
}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(1_000));
|
||||
}
|
||||
if !exited {
|
||||
log::error!("Failed to wait child xorg, after kill()");
|
||||
// try kill -9?
|
||||
}
|
||||
}
|
||||
log::debug!("Try kill child process wm");
|
||||
if let Ok(_) = child_wm.kill() {
|
||||
let mut exited = false;
|
||||
for _ in 0..2 {
|
||||
match child_wm.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
// Logout may result "wm exit with signal: 11 (SIGSEGV) (core dumped)"
|
||||
log::info!("wm exit with {}", status);
|
||||
exited = true;
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
log::error!("Failed to wait wm process, {}", e);
|
||||
Self::fatal_exit();
|
||||
}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(1_000));
|
||||
}
|
||||
if !exited {
|
||||
log::error!("Failed to wait child xorg, after kill()");
|
||||
// try kill -9?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_wait_stop_x11(child_xorg: &mut Child, child_wm: &mut Child) -> bool {
|
||||
let mut desktop_manager = DESKTOP_MANAGER.lock().unwrap();
|
||||
let mut exited = true;
|
||||
if let Some(desktop_manager) = &mut (*desktop_manager) {
|
||||
if desktop_manager.child_exit.load(Ordering::SeqCst) {
|
||||
exited = true;
|
||||
} else {
|
||||
exited = Self::try_wait_x11_child_exit(child_xorg, child_wm);
|
||||
}
|
||||
if exited {
|
||||
log::debug!("Wait x11 children exiting");
|
||||
Self::wait_x11_children_exit(child_xorg, child_wm);
|
||||
desktop_manager
|
||||
.is_child_running
|
||||
.store(false, Ordering::SeqCst);
|
||||
desktop_manager.child_exit.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
exited
|
||||
}
|
||||
|
||||
fn wait_stop_x11(mut child_xorg: Child, mut child_wm: Child) {
|
||||
loop {
|
||||
if Self::try_wait_stop_x11(&mut child_xorg, &mut child_wm) {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_xorg() -> &'static str {
|
||||
// Fedora 26 or later
|
||||
let xorg = "/usr/libexec/Xorg";
|
||||
if Path::new(xorg).is_file() {
|
||||
return xorg;
|
||||
}
|
||||
// Debian 9 or later
|
||||
let xorg = "/usr/lib/xorg/Xorg";
|
||||
if Path::new(xorg).is_file() {
|
||||
return xorg;
|
||||
}
|
||||
// Ubuntu 16.04 or later
|
||||
let xorg = "/usr/lib/xorg/Xorg";
|
||||
if Path::new(xorg).is_file() {
|
||||
return xorg;
|
||||
}
|
||||
// Arch Linux
|
||||
let xorg = "/usr/lib/xorg-server/Xorg";
|
||||
if Path::new(xorg).is_file() {
|
||||
return xorg;
|
||||
}
|
||||
// Arch Linux
|
||||
let xorg = "/usr/lib/Xorg";
|
||||
if Path::new(xorg).is_file() {
|
||||
return xorg;
|
||||
}
|
||||
// CentOS 7 /usr/bin/Xorg or param=Xorg
|
||||
|
||||
log::warn!("Failed to find xorg, use default Xorg.\n Please add \"allowed_users=anybody\" to \"/etc/X11/Xwrapper.config\".");
|
||||
"Xorg"
|
||||
}
|
||||
|
||||
fn start_x_server(
|
||||
xauth: &str,
|
||||
display: &str,
|
||||
uid: u32,
|
||||
gid: u32,
|
||||
envs: &HashMap<&str, String>,
|
||||
) -> ResultType<Child> {
|
||||
let xorg = Self::get_xorg();
|
||||
log::info!("Use xorg: {}", &xorg);
|
||||
match Command::new(xorg)
|
||||
.envs(envs)
|
||||
.uid(uid)
|
||||
.gid(gid)
|
||||
.args(vec![
|
||||
"-noreset",
|
||||
"+extension",
|
||||
"GLX",
|
||||
"+extension",
|
||||
"RANDR",
|
||||
"+extension",
|
||||
"RENDER",
|
||||
//"-logfile",
|
||||
//"/tmp/RustDesk_xorg.log",
|
||||
"-config",
|
||||
"/etc/rustdesk/xorg.conf",
|
||||
"-auth",
|
||||
xauth,
|
||||
display,
|
||||
])
|
||||
.spawn()
|
||||
{
|
||||
Ok(c) => Ok(c),
|
||||
Err(e) => {
|
||||
bail!("Failed to start Xorg with display {}, {}", display, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_x_window_manager(
|
||||
uid: u32,
|
||||
gid: u32,
|
||||
envs: &HashMap<&str, String>,
|
||||
) -> ResultType<Child> {
|
||||
match Command::new("/etc/rustdesk/startwm.sh")
|
||||
.envs(envs)
|
||||
.uid(uid)
|
||||
.gid(gid)
|
||||
.spawn()
|
||||
{
|
||||
Ok(c) => Ok(c),
|
||||
Err(e) => {
|
||||
bail!("Failed to start window manager, {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_children(&mut self) {
|
||||
self.child_exit.store(true, Ordering::SeqCst);
|
||||
for _i in 1..10 {
|
||||
if !self.is_child_running.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
}
|
||||
if self.is_child_running.load(Ordering::SeqCst) {
|
||||
log::warn!("xdesktop child is still running!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pam_get_service_name() -> &'static str {
|
||||
if Path::new("/etc/pam.d/rustdesk").is_file() {
|
||||
"rustdesk"
|
||||
} else {
|
||||
"gdm"
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,13 @@ pub mod delegate;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
pub mod linux_desktop_manager;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::{message_proto::CursorData, ResultType};
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))]
|
||||
const SERVICE_INTERVAL: u64 = 300;
|
||||
|
||||
pub fn is_xfce() -> bool {
|
||||
@@ -35,7 +39,9 @@ pub fn is_xfce() -> bool {
|
||||
|
||||
pub fn breakdown_callback() {
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::input_service::clear_remapped_keycode()
|
||||
crate::input_service::clear_remapped_keycode();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::input_service::release_device_modifiers();
|
||||
}
|
||||
|
||||
// Android
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use super::{CursorData, ResultType};
|
||||
use crate::common::PORTABLE_APPNAME_RUNTIME_ENV_KEY;
|
||||
use crate::ipc;
|
||||
use crate::license::*;
|
||||
use crate::{
|
||||
ipc,
|
||||
license::*,
|
||||
privacy_win_mag::{self, WIN_MAG_INJECTED_PROCESS_EXE},
|
||||
};
|
||||
use hbb_common::{
|
||||
allow_err, bail,
|
||||
config::{self, Config},
|
||||
@@ -9,16 +12,17 @@ use hbb_common::{
|
||||
message_proto::Resolution,
|
||||
sleep, timeout, tokio,
|
||||
};
|
||||
use std::io::prelude::*;
|
||||
use std::ptr::null_mut;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::OsString,
|
||||
fs, io, mem,
|
||||
fs, io,
|
||||
io::prelude::*,
|
||||
mem,
|
||||
os::windows::process::CommandExt,
|
||||
path::PathBuf,
|
||||
path::*,
|
||||
ptr::null_mut,
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
collections::HashMap
|
||||
};
|
||||
use winapi::{
|
||||
ctypes::c_void,
|
||||
@@ -717,7 +721,7 @@ pub fn is_share_rdp() -> bool {
|
||||
}
|
||||
|
||||
pub fn set_share_rdp(enable: bool) {
|
||||
let (subkey, _, _, _) = get_install_info();
|
||||
let (subkey, _, _, _, _) = get_install_info();
|
||||
let cmd = format!(
|
||||
"reg add {} /f /v share_rdp /t REG_SZ /d \"{}\"",
|
||||
subkey,
|
||||
@@ -810,11 +814,11 @@ fn get_valid_subkey() -> String {
|
||||
return get_subkey(&app_name, false);
|
||||
}
|
||||
|
||||
pub fn get_install_info() -> (String, String, String, String) {
|
||||
pub fn get_install_info() -> (String, String, String, String, String) {
|
||||
get_install_info_with_subkey(get_valid_subkey())
|
||||
}
|
||||
|
||||
fn get_default_install_info() -> (String, String, String, String) {
|
||||
fn get_default_install_info() -> (String, String, String, String, String) {
|
||||
get_install_info_with_subkey(get_subkey(&crate::get_app_name(), false))
|
||||
}
|
||||
|
||||
@@ -837,8 +841,8 @@ fn get_default_install_path() -> String {
|
||||
|
||||
pub fn check_update_broker_process() -> ResultType<()> {
|
||||
// let (_, path, _, _) = get_install_info();
|
||||
let process_exe = crate::win_privacy::INJECTED_PROCESS_EXE;
|
||||
let origin_process_exe = crate::win_privacy::ORIGIN_PROCESS_EXE;
|
||||
let process_exe = privacy_win_mag::INJECTED_PROCESS_EXE;
|
||||
let origin_process_exe = privacy_win_mag::ORIGIN_PROCESS_EXE;
|
||||
|
||||
let exe_file = std::env::current_exe()?;
|
||||
if exe_file.parent().is_none() {
|
||||
@@ -883,7 +887,7 @@ pub fn check_update_broker_process() -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_install_info_with_subkey(subkey: String) -> (String, String, String, String) {
|
||||
fn get_install_info_with_subkey(subkey: String) -> (String, String, String, String, String) {
|
||||
let mut path = get_reg_of(&subkey, "InstallLocation");
|
||||
if path.is_empty() {
|
||||
path = get_default_install_path();
|
||||
@@ -894,43 +898,59 @@ fn get_install_info_with_subkey(subkey: String) -> (String, String, String, Stri
|
||||
crate::get_app_name()
|
||||
);
|
||||
let exe = format!("{}\\{}.exe", path, crate::get_app_name());
|
||||
(subkey, path, start_menu, exe)
|
||||
let dll = format!("{}\\sciter.dll", path);
|
||||
(subkey, path, start_menu, exe, dll)
|
||||
}
|
||||
|
||||
pub fn copy_exe_cmd(src_exe: &str, _exe: &str, path: &str) -> String {
|
||||
pub fn copy_raw_cmd(src_raw: &str, _raw: &str, _path: &str) -> String {
|
||||
#[cfg(feature = "flutter")]
|
||||
let main_exe = format!(
|
||||
let main_raw = format!(
|
||||
"XCOPY \"{}\" \"{}\" /Y /E /H /C /I /K /R /Z",
|
||||
PathBuf::from(src_exe)
|
||||
PathBuf::from(src_raw)
|
||||
.parent()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
path
|
||||
_path
|
||||
);
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
let main_exe = format!(
|
||||
"copy /Y \"{src_exe}\" \"{exe}\"",
|
||||
src_exe = src_exe,
|
||||
exe = _exe
|
||||
let main_raw = format!(
|
||||
"copy /Y \"{src_raw}\" \"{raw}\"",
|
||||
src_raw = src_raw,
|
||||
raw = _raw
|
||||
);
|
||||
return main_raw;
|
||||
}
|
||||
|
||||
return format!(
|
||||
pub fn copy_exe_cmd(src_exe: &str, exe: &str, path: &str) -> String {
|
||||
let main_exe = copy_raw_cmd(src_exe, exe, path);
|
||||
format!(
|
||||
"
|
||||
{main_exe}
|
||||
copy /Y \"{ORIGIN_PROCESS_EXE}\" \"{path}\\{broker_exe}\"
|
||||
\"{src_exe}\" --extract \"{path}\"
|
||||
",
|
||||
main_exe = main_exe,
|
||||
path = path,
|
||||
ORIGIN_PROCESS_EXE = crate::win_privacy::ORIGIN_PROCESS_EXE,
|
||||
broker_exe = crate::win_privacy::INJECTED_PROCESS_EXE,
|
||||
);
|
||||
ORIGIN_PROCESS_EXE = privacy_win_mag::ORIGIN_PROCESS_EXE,
|
||||
broker_exe = privacy_win_mag::INJECTED_PROCESS_EXE,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_me() -> ResultType<()> {
|
||||
let (_, path, _, exe) = get_install_info();
|
||||
let (_, path, _, exe, _dll) = get_install_info();
|
||||
let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned();
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
let src_dll = std::env::current_exe()?
|
||||
.parent()
|
||||
.unwrap_or(&Path::new(&get_default_install_path()))
|
||||
.join("sciter.dll")
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.to_owned();
|
||||
#[cfg(feature = "flutter")]
|
||||
let copy_dll = "".to_string();
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
let copy_dll = copy_raw_cmd(&src_dll, &_dll, &path);
|
||||
let cmds = format!(
|
||||
"
|
||||
chcp 65001
|
||||
@@ -938,11 +958,13 @@ pub fn update_me() -> ResultType<()> {
|
||||
taskkill /F /IM {broker_exe}
|
||||
taskkill /F /IM {app_name}.exe /FI \"PID ne {cur_pid}\"
|
||||
{copy_exe}
|
||||
{copy_dll}
|
||||
sc start {app_name}
|
||||
{lic}
|
||||
",
|
||||
copy_exe = copy_exe_cmd(&src_exe, &exe, &path),
|
||||
broker_exe = crate::win_privacy::INJECTED_PROCESS_EXE,
|
||||
copy_dll = copy_dll,
|
||||
broker_exe = WIN_MAG_INJECTED_PROCESS_EXE,
|
||||
app_name = crate::get_app_name(),
|
||||
lic = register_licence(),
|
||||
cur_pid = get_current_pid(),
|
||||
@@ -980,12 +1002,14 @@ 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(false);
|
||||
let mut path = path.trim_end_matches('\\').to_owned();
|
||||
let (subkey, _path, start_menu, exe) = get_default_install_info();
|
||||
let (subkey, _path, start_menu, exe, dll) = get_default_install_info();
|
||||
let mut exe = exe;
|
||||
let mut _dll = dll;
|
||||
if path.is_empty() {
|
||||
path = _path;
|
||||
} else {
|
||||
exe = exe.replace(&_path, &path);
|
||||
_dll = _dll.replace(&_path, &path);
|
||||
}
|
||||
let mut version_major = "0";
|
||||
let mut version_minor = "0";
|
||||
@@ -1109,6 +1133,18 @@ if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name}
|
||||
app_name = crate::get_app_name(),
|
||||
);
|
||||
let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_string();
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
let src_dll = std::env::current_exe()?
|
||||
.parent()
|
||||
.unwrap_or(&Path::new(&get_default_install_path()))
|
||||
.join("sciter.dll")
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.to_owned();
|
||||
#[cfg(feature = "flutter")]
|
||||
let copy_dll = "".to_string();
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
let copy_dll = copy_raw_cmd(&src_dll, &_dll, &path);
|
||||
|
||||
let install_cert = if options.contains("driverCert") {
|
||||
format!("\"{}\" --install-cert \"RustDeskIddDriver.cer\"", src_exe)
|
||||
@@ -1122,6 +1158,7 @@ if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name}
|
||||
chcp 65001
|
||||
md \"{path}\"
|
||||
{copy_exe}
|
||||
{copy_dll}
|
||||
reg add {subkey} /f
|
||||
reg add {subkey} /f /v DisplayIcon /t REG_SZ /d \"{exe}\"
|
||||
reg add {subkey} /f /v DisplayName /t REG_SZ /d \"{app_name}\"
|
||||
@@ -1181,6 +1218,7 @@ sc delete {app_name}
|
||||
&dels
|
||||
},
|
||||
copy_exe = copy_exe_cmd(&src_exe, &exe, &path),
|
||||
copy_dll = copy_dll
|
||||
);
|
||||
run_cmds(cmds, debug, "install")?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(2000));
|
||||
@@ -1193,7 +1231,7 @@ sc delete {app_name}
|
||||
}
|
||||
|
||||
pub fn run_after_install() -> ResultType<()> {
|
||||
let (_, _, _, exe) = get_install_info();
|
||||
let (_, _, _, exe, _) = get_install_info();
|
||||
run_cmds(get_after_install(&exe), true, "after_install")
|
||||
}
|
||||
|
||||
@@ -1220,14 +1258,14 @@ fn get_before_uninstall(kill_self: bool) -> String {
|
||||
netsh advfirewall firewall delete rule name=\"{app_name} Service\"
|
||||
",
|
||||
app_name = app_name,
|
||||
broker_exe = crate::win_privacy::INJECTED_PROCESS_EXE,
|
||||
broker_exe = WIN_MAG_INJECTED_PROCESS_EXE,
|
||||
ext = ext,
|
||||
filter = filter,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_uninstall(kill_self: bool) -> String {
|
||||
let (subkey, path, start_menu, _) = get_install_info();
|
||||
let (subkey, path, start_menu, _, _) = get_install_info();
|
||||
format!(
|
||||
"
|
||||
{before_uninstall}
|
||||
@@ -1331,7 +1369,7 @@ pub fn is_installed() -> bool {
|
||||
service::ServiceAccess,
|
||||
service_manager::{ServiceManager, ServiceManagerAccess},
|
||||
};
|
||||
let (_, _, _, exe) = get_install_info();
|
||||
let (_, _, _, exe, _) = get_install_info();
|
||||
if !std::fs::metadata(exe).is_ok() {
|
||||
return false;
|
||||
}
|
||||
@@ -1347,7 +1385,7 @@ pub fn is_installed() -> bool {
|
||||
}
|
||||
|
||||
pub fn get_installed_version() -> String {
|
||||
let (_, _, _, exe) = get_install_info();
|
||||
let (_, _, _, exe, _) = get_install_info();
|
||||
if let Ok(output) = std::process::Command::new(exe).arg("--version").output() {
|
||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||
return line.to_owned();
|
||||
@@ -1357,7 +1395,7 @@ pub fn get_installed_version() -> String {
|
||||
}
|
||||
|
||||
fn get_reg(name: &str) -> String {
|
||||
let (subkey, _, _, _) = get_install_info();
|
||||
let (subkey, _, _, _, _) = get_install_info();
|
||||
get_reg_of(&subkey, name)
|
||||
}
|
||||
|
||||
@@ -1408,7 +1446,7 @@ pub fn bootstrap() {
|
||||
}
|
||||
|
||||
fn register_licence() -> String {
|
||||
let (subkey, _, _, _) = get_install_info();
|
||||
let (subkey, _, _, _, _) = get_install_info();
|
||||
if let Ok(lic) = get_license_from_exe_name() {
|
||||
format!(
|
||||
"
|
||||
@@ -1765,14 +1803,17 @@ pub fn send_message_to_hnwd(
|
||||
|
||||
pub fn create_process_with_logon(user: &str, pwd: &str, exe: &str, arg: &str) -> ResultType<()> {
|
||||
let last_error_table = HashMap::from([
|
||||
(ERROR_LOGON_FAILURE, "The user name or password is incorrect."),
|
||||
(ERROR_ACCESS_DENIED, "Access is denied.")
|
||||
(
|
||||
ERROR_LOGON_FAILURE,
|
||||
"The user name or password is incorrect.",
|
||||
),
|
||||
(ERROR_ACCESS_DENIED, "Access is denied."),
|
||||
]);
|
||||
|
||||
unsafe {
|
||||
let user_split = user.split("\\").collect::<Vec<&str>>();
|
||||
let wuser = wide_string(user_split.get(1).unwrap_or(&user));
|
||||
let wpc = wide_string(user_split.get(0).unwrap_or(&""));
|
||||
let wpc = wide_string(user_split.get(0).unwrap_or(&""));
|
||||
let wpwd = wide_string(pwd);
|
||||
let cmd = if arg.is_empty() {
|
||||
format!("\"{}\"", exe)
|
||||
@@ -1804,7 +1845,7 @@ pub fn create_process_with_logon(user: &str, pwd: &str, exe: &str, arg: &str) ->
|
||||
{
|
||||
let last_error = GetLastError();
|
||||
bail!(
|
||||
"CreateProcessWithLogonW failed : \"{}\", errno={}",
|
||||
"CreateProcessWithLogonW failed : \"{}\", errno={}",
|
||||
last_error_table
|
||||
.get(&last_error)
|
||||
.unwrap_or(&"Unknown error"),
|
||||
@@ -2091,7 +2132,25 @@ mod cert {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_char_by_vk(vk: u32) -> Option<char> {
|
||||
#[inline]
|
||||
pub fn get_char_from_vk(vk: u32) -> Option<char> {
|
||||
get_char_from_unicode(get_unicode_from_vk(vk)?)
|
||||
}
|
||||
|
||||
pub fn get_char_from_unicode(unicode: u16) -> Option<char> {
|
||||
let buff = [unicode];
|
||||
if let Some(chr) = String::from_utf16(&buff[..1]).ok()?.chars().next() {
|
||||
if chr.is_control() {
|
||||
return None;
|
||||
} else {
|
||||
Some(chr)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_unicode_from_vk(vk: u32) -> Option<u16> {
|
||||
const BUF_LEN: i32 = 32;
|
||||
let mut buff = [0_u16; BUF_LEN as usize];
|
||||
let buff_ptr = buff.as_mut_ptr();
|
||||
@@ -2116,24 +2175,20 @@ pub fn get_char_by_vk(vk: u32) -> Option<char> {
|
||||
ToUnicodeEx(vk, 0x00, &state as _, buff_ptr, BUF_LEN, 0, layout)
|
||||
};
|
||||
if len == 1 {
|
||||
if let Some(chr) = String::from_utf16(&buff[..len as usize])
|
||||
.ok()?
|
||||
.chars()
|
||||
.next()
|
||||
{
|
||||
if chr.is_control() {
|
||||
return None;
|
||||
} else {
|
||||
Some(chr)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(buff[0])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_process_consent_running() -> ResultType<bool> {
|
||||
let output = std::process::Command::new("cmd")
|
||||
.args(&["/C", "tasklist | findstr consent.exe"])
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output()?;
|
||||
Ok(output.status.success() && !output.stdout.is_empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -2151,10 +2206,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_char_by_vk() {
|
||||
let chr = get_char_by_vk(0x41); // VK_A
|
||||
fn test_get_unicode_char_by_vk() {
|
||||
let chr = get_char_from_vk(0x41); // VK_A
|
||||
assert_eq!(chr, Some('a'));
|
||||
let chr = get_char_by_vk(VK_ESCAPE as u32); // VK_ESC
|
||||
let chr = get_char_from_vk(VK_ESCAPE as u32); // VK_ESC
|
||||
assert_eq!(chr, None)
|
||||
}
|
||||
}
|
||||
|
||||
111
src/plugin/callback_msg.rs
Normal file
111
src/plugin/callback_msg.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use super::cstr_to_string;
|
||||
use crate::flutter::{self, APP_TYPE_CM, APP_TYPE_MAIN, SESSIONS};
|
||||
use hbb_common::{lazy_static, log, message_proto::PluginRequest};
|
||||
use serde_json;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{c_char, c_void},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
const MSG_TO_PEER_TARGET: &str = "peer";
|
||||
const MSG_TO_UI_TARGET: &str = "ui";
|
||||
|
||||
#[allow(dead_code)]
|
||||
const MSG_TO_UI_FLUTTER_CHANNEL_MAIN: u16 = 0x01 << 0;
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
const MSG_TO_UI_FLUTTER_CHANNEL_CM: u16 = 0x01 << 1;
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
const MSG_TO_UI_FLUTTER_CHANNEL_CM: u16 = 0x01;
|
||||
const MSG_TO_UI_FLUTTER_CHANNEL_REMOTE: u16 = 0x01 << 2;
|
||||
#[allow(dead_code)]
|
||||
const MSG_TO_UI_FLUTTER_CHANNEL_TRANSFER: u16 = 0x01 << 3;
|
||||
#[allow(dead_code)]
|
||||
const MSG_TO_UI_FLUTTER_CHANNEL_FORWARD: u16 = 0x01 << 4;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref MSG_TO_UI_FLUTTER_CHANNELS: Arc<HashMap<u16, String>> = {
|
||||
let channels = HashMap::from([
|
||||
(MSG_TO_UI_FLUTTER_CHANNEL_MAIN, APP_TYPE_MAIN.to_string()),
|
||||
(MSG_TO_UI_FLUTTER_CHANNEL_CM, APP_TYPE_CM.to_string()),
|
||||
]);
|
||||
Arc::new(channels)
|
||||
};
|
||||
}
|
||||
|
||||
/// Callback to send message to peer or ui.
|
||||
/// peer, target, id are utf8 strings(null terminated).
|
||||
///
|
||||
/// peer: The peer id.
|
||||
/// target: "peer" or "ui".
|
||||
/// id: The id of this plugin.
|
||||
/// content: The content.
|
||||
/// len: The length of the content.
|
||||
pub fn callback_msg(
|
||||
peer: *const c_char,
|
||||
target: *const c_char,
|
||||
id: *const c_char,
|
||||
content: *const c_void,
|
||||
len: usize,
|
||||
) {
|
||||
macro_rules! callback_msg_field {
|
||||
($field: ident) => {
|
||||
let $field = match cstr_to_string($field) {
|
||||
Err(e) => {
|
||||
log::error!("Failed to convert {} to string, {}", stringify!($field), e);
|
||||
return;
|
||||
}
|
||||
Ok(v) => v,
|
||||
};
|
||||
};
|
||||
}
|
||||
callback_msg_field!(peer);
|
||||
callback_msg_field!(target);
|
||||
callback_msg_field!(id);
|
||||
|
||||
match &target as _ {
|
||||
MSG_TO_PEER_TARGET => {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&peer) {
|
||||
let content_slice =
|
||||
unsafe { std::slice::from_raw_parts(content as *const u8, len) };
|
||||
let content_vec = Vec::from(content_slice);
|
||||
let request = PluginRequest {
|
||||
id,
|
||||
content: bytes::Bytes::from(content_vec),
|
||||
..Default::default()
|
||||
};
|
||||
session.send_plugin_request(request);
|
||||
}
|
||||
}
|
||||
MSG_TO_UI_TARGET => {
|
||||
let content_slice = unsafe { std::slice::from_raw_parts(content as *const u8, len) };
|
||||
let channel = u16::from_be_bytes([content_slice[0], content_slice[1]]);
|
||||
let content = std::string::String::from_utf8(content_slice[2..].to_vec())
|
||||
.unwrap_or("".to_string());
|
||||
let mut m = HashMap::new();
|
||||
m.insert("name", "plugin_event");
|
||||
m.insert("peer", &peer);
|
||||
m.insert("content", &content);
|
||||
let event = serde_json::to_string(&m).unwrap_or("".to_string());
|
||||
for (k, v) in MSG_TO_UI_FLUTTER_CHANNELS.iter() {
|
||||
if channel & k != 0 {
|
||||
let _res = flutter::push_global_event(v as _, event.clone());
|
||||
}
|
||||
}
|
||||
if channel & MSG_TO_UI_FLUTTER_CHANNEL_REMOTE != 0
|
||||
|| channel & MSG_TO_UI_FLUTTER_CHANNEL_TRANSFER != 0
|
||||
|| channel & MSG_TO_UI_FLUTTER_CHANNEL_FORWARD != 0
|
||||
{
|
||||
let _res = flutter::push_session_event(
|
||||
&peer,
|
||||
"plugin_event",
|
||||
vec![("peer", &peer), ("content", &content)],
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::error!("Unknown target {}", target);
|
||||
}
|
||||
}
|
||||
}
|
||||
196
src/plugin/config.rs
Normal file
196
src/plugin/config.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
use super::desc::ConfigItem;
|
||||
use hbb_common::{bail, config::Config as HbbConfig, lazy_static, ResultType};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{Arc, Mutex},
|
||||
{collections::HashMap, path::PathBuf},
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CONFIG_LOCAL: Arc<Mutex<HashMap<String, LocalConfig>>> = Default::default();
|
||||
static ref CONFIG_LOCAL_ITEMS: Arc<Mutex<HashMap<String, Vec<ConfigItem>>>> = Default::default();
|
||||
static ref CONFIG_PEERS: Arc<Mutex<HashMap<String, PeersConfig>>> = Default::default();
|
||||
static ref CONFIG_PEER_ITEMS: Arc<Mutex<HashMap<String, Vec<ConfigItem>>>> = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct LocalConfig(HashMap<String, String>);
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct PeerConfig(HashMap<String, String>);
|
||||
type PeersConfig = HashMap<String, PeerConfig>;
|
||||
|
||||
#[inline]
|
||||
fn path_plugins(id: &str) -> PathBuf {
|
||||
HbbConfig::path("plugins").join(id)
|
||||
}
|
||||
|
||||
impl Deref for LocalConfig {
|
||||
type Target = HashMap<String, String>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for LocalConfig {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PeerConfig {
|
||||
type Target = HashMap<String, String>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for PeerConfig {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalConfig {
|
||||
#[inline]
|
||||
fn path(id: &str) -> PathBuf {
|
||||
path_plugins(id).join("local.toml")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn load(id: &str) {
|
||||
let mut conf = hbb_common::config::load_path::<LocalConfig>(Self::path(id));
|
||||
if let Some(items) = CONFIG_LOCAL_ITEMS.lock().unwrap().get(id) {
|
||||
for item in items {
|
||||
if !conf.contains_key(&item.key) {
|
||||
conf.insert(item.key.to_owned(), item.default.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
CONFIG_LOCAL.lock().unwrap().insert(id.to_owned(), conf);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn save(id: &str) -> ResultType<()> {
|
||||
match CONFIG_LOCAL.lock().unwrap().get(id) {
|
||||
Some(config) => hbb_common::config::store_path(Self::path(id), config),
|
||||
None => bail!("No such plugin {}", id),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(id: &str, key: &str) -> Option<String> {
|
||||
if let Some(conf) = CONFIG_LOCAL.lock().unwrap().get(id) {
|
||||
return conf.get(key).map(|s| s.to_owned());
|
||||
}
|
||||
Self::load(id);
|
||||
CONFIG_LOCAL
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(id)?
|
||||
.get(key)
|
||||
.map(|s| s.to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set(id: &str, key: &str, value: &str) -> ResultType<()> {
|
||||
match CONFIG_LOCAL.lock().unwrap().get_mut(id) {
|
||||
Some(config) => {
|
||||
config.insert(key.to_owned(), value.to_owned());
|
||||
hbb_common::config::store_path(Self::path(id), config)
|
||||
}
|
||||
None => bail!("No such plugin {}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerConfig {
|
||||
#[inline]
|
||||
fn path(id: &str, peer: &str) -> PathBuf {
|
||||
path_plugins(id)
|
||||
.join("peers")
|
||||
.join(format!("{}.toml", peer))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn load(id: &str, peer: &str) {
|
||||
let mut conf = hbb_common::config::load_path::<PeerConfig>(Self::path(id, peer));
|
||||
if let Some(items) = CONFIG_PEER_ITEMS.lock().unwrap().get(id) {
|
||||
for item in items {
|
||||
if !conf.contains_key(&item.key) {
|
||||
conf.insert(item.key.to_owned(), item.default.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
match CONFIG_PEERS.lock().unwrap().get_mut(id) {
|
||||
Some(peers) => {
|
||||
peers.insert(peer.to_owned(), conf);
|
||||
}
|
||||
None => {
|
||||
let mut peers = HashMap::new();
|
||||
peers.insert(peer.to_owned(), conf);
|
||||
CONFIG_PEERS.lock().unwrap().insert(id.to_owned(), peers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn save(id: &str, peer: &str) -> ResultType<()> {
|
||||
match CONFIG_PEERS.lock().unwrap().get(id) {
|
||||
Some(peers) => match peers.get(peer) {
|
||||
Some(config) => hbb_common::config::store_path(Self::path(id, peer), config),
|
||||
None => bail!("No such peer {}", peer),
|
||||
},
|
||||
None => bail!("No such plugin {}", id),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(id: &str, peer: &str, key: &str) -> Option<String> {
|
||||
if let Some(peers) = CONFIG_PEERS.lock().unwrap().get(id) {
|
||||
if let Some(conf) = peers.get(peer) {
|
||||
return conf.get(key).map(|s| s.to_owned());
|
||||
}
|
||||
}
|
||||
Self::load(id, peer);
|
||||
CONFIG_PEERS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(id)?
|
||||
.get(peer)?
|
||||
.get(key)
|
||||
.map(|s| s.to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set(id: &str, peer: &str, key: &str, value: &str) -> ResultType<()> {
|
||||
match CONFIG_PEERS.lock().unwrap().get_mut(id) {
|
||||
Some(peers) => match peers.get_mut(peer) {
|
||||
Some(config) => {
|
||||
config.insert(key.to_owned(), value.to_owned());
|
||||
hbb_common::config::store_path(Self::path(id, peer), config)
|
||||
}
|
||||
None => bail!("No such peer {}", peer),
|
||||
},
|
||||
None => bail!("No such plugin {}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn set_local_items(id: &str, items: &Vec<ConfigItem>) {
|
||||
CONFIG_LOCAL_ITEMS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(id.to_owned(), items.clone());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn set_peer_items(id: &str, items: &Vec<ConfigItem>) {
|
||||
CONFIG_PEER_ITEMS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(id.to_owned(), items.clone());
|
||||
}
|
||||
119
src/plugin/desc.rs
Normal file
119
src/plugin/desc.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use hbb_common::ResultType;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{c_char, CStr};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UiButton {
|
||||
key: String,
|
||||
text: String,
|
||||
icon: String, // icon can be int in flutter, but string in other ui framework. And it is flexible to use string.
|
||||
tooltip: String,
|
||||
action: String, // The action to be triggered when the button is clicked.
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UiCheckbox {
|
||||
key: String,
|
||||
text: String,
|
||||
tooltip: String,
|
||||
action: String, // The action to be triggered when the checkbox is checked or unchecked.
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum UiType {
|
||||
Button(UiButton),
|
||||
Checkbox(UiCheckbox),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Location {
|
||||
pub ui: HashMap<String, UiType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ConfigItem {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
pub default: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub local: Vec<ConfigItem>,
|
||||
pub peer: Vec<ConfigItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Desc {
|
||||
id: String,
|
||||
name: String,
|
||||
version: String,
|
||||
description: String,
|
||||
author: String,
|
||||
home: String,
|
||||
license: String,
|
||||
published: String,
|
||||
released: String,
|
||||
github: String,
|
||||
location: Location,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Desc {
|
||||
pub fn from_cstr(s: *const c_char) -> ResultType<Self> {
|
||||
let s = unsafe { CStr::from_ptr(s) };
|
||||
Ok(serde_json::from_str(s.to_str()?)?)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn version(&self) -> &str {
|
||||
&self.version
|
||||
}
|
||||
|
||||
pub fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
|
||||
pub fn author(&self) -> &str {
|
||||
&self.author
|
||||
}
|
||||
|
||||
pub fn home(&self) -> &str {
|
||||
&self.home
|
||||
}
|
||||
|
||||
pub fn license(&self) -> &str {
|
||||
&self.license
|
||||
}
|
||||
|
||||
pub fn published(&self) -> &str {
|
||||
&self.published
|
||||
}
|
||||
|
||||
pub fn released(&self) -> &str {
|
||||
&self.released
|
||||
}
|
||||
|
||||
pub fn github(&self) -> &str {
|
||||
&self.github
|
||||
}
|
||||
|
||||
pub fn location(&self) -> &Location {
|
||||
&self.location
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &Config {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
28
src/plugin/errno.rs
Normal file
28
src/plugin/errno.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub const ERR_SUCCESS: i32 = 0;
|
||||
|
||||
// ======================================================
|
||||
// errors that will be handled by RustDesk
|
||||
|
||||
pub const ERR_RUSTDESK_HANDLE_BASE: i32 = 10000;
|
||||
|
||||
// not loaded
|
||||
pub const ERR_PLUGIN_LOAD: i32 = 10001;
|
||||
// not initialized
|
||||
pub const ERR_PLUGIN_MSG_CB: i32 = 10101;
|
||||
// invalid
|
||||
pub const ERR_CALL_INVALID_METHOD: i32 = 10201;
|
||||
pub const ERR_CALL_NOT_SUPPORTED_METHOD: i32 = 10202;
|
||||
// failed on calling
|
||||
pub const ERR_CALL_INVALID_ARGS: i32 = 10301;
|
||||
pub const ERR_PEER_ID_MISMATCH: i32 = 10302;
|
||||
|
||||
// ======================================================
|
||||
// errors that should be handled by the plugin
|
||||
|
||||
pub const ERR_PLUGIN_HANDLE_BASE: i32 = 20000;
|
||||
|
||||
pub const EER_CALL_FAILED: i32 = 200021;
|
||||
pub const ERR_PEER_ON_FAILED: i32 = 30012;
|
||||
pub const ERR_PEER_OFF_FAILED: i32 = 30012;
|
||||
34
src/plugin/mod.rs
Normal file
34
src/plugin/mod.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use hbb_common::ResultType;
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
|
||||
mod callback_msg;
|
||||
mod config;
|
||||
pub mod desc;
|
||||
mod errno;
|
||||
mod plugins;
|
||||
|
||||
pub use plugins::{
|
||||
handle_client_event, handle_server_event, handle_ui_event, load_plugin, load_plugins,
|
||||
reload_plugin, unload_plugin,
|
||||
};
|
||||
|
||||
pub use config::{LocalConfig, PeerConfig};
|
||||
|
||||
#[inline]
|
||||
fn cstr_to_string(cstr: *const c_char) -> ResultType<String> {
|
||||
Ok(String::from_utf8(unsafe {
|
||||
CStr::from_ptr(cstr).to_bytes().to_vec()
|
||||
})?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_code_msg_from_ret(ret: *const c_void) -> (i32, String) {
|
||||
assert!(ret.is_null() == false);
|
||||
let code_bytes = unsafe { std::slice::from_raw_parts(ret as *const u8, 4) };
|
||||
let code = i32::from_le_bytes([code_bytes[0], code_bytes[1], code_bytes[2], code_bytes[3]]);
|
||||
let msg = unsafe { CStr::from_ptr((ret as *const u8).add(4) as _) }
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
(code, msg)
|
||||
}
|
||||
315
src/plugin/plugins.rs
Normal file
315
src/plugin/plugins.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{c_char, c_void},
|
||||
path::Path,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use super::{callback_msg, desc::Desc, errno::*, get_code_msg_from_ret};
|
||||
use crate::flutter;
|
||||
use hbb_common::{
|
||||
bail,
|
||||
dlopen::symbor::Library,
|
||||
lazy_static, libc, log,
|
||||
message_proto::{Message, Misc, PluginResponse},
|
||||
ResultType,
|
||||
};
|
||||
|
||||
const METHOD_HANDLE_UI: &[u8; 10] = b"handle_ui\0";
|
||||
const METHOD_HANDLE_PEER: &[u8; 12] = b"handle_peer\0";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref PLUGINS: Arc<RwLock<HashMap<String, Plugin>>> = Default::default();
|
||||
}
|
||||
|
||||
/// Initialize the plugins.
|
||||
///
|
||||
/// Return null ptr if success.
|
||||
/// Return the error message if failed. `i32-String` without dash, i32 is a signed little-endian number, the String is utf8 string.
|
||||
/// The plugin allocate memory with `libc::malloc` and return the pointer.
|
||||
pub type PluginFuncInit = fn() -> *const c_void;
|
||||
/// Reset the plugin.
|
||||
///
|
||||
/// Return null ptr if success.
|
||||
/// Return the error message if failed. `i32-String` without dash, i32 is a signed little-endian number, the String is utf8 string.
|
||||
/// The plugin allocate memory with `libc::malloc` and return the pointer.
|
||||
pub type PluginFuncReset = fn() -> *const c_void;
|
||||
/// Clear the plugin.
|
||||
///
|
||||
/// Return null ptr if success.
|
||||
/// Return the error message if failed. `i32-String` without dash, i32 is a signed little-endian number, the String is utf8 string.
|
||||
/// The plugin allocate memory with `libc::malloc` and return the pointer.
|
||||
pub type PluginFuncClear = fn() -> *const c_void;
|
||||
/// Get the description of the plugin.
|
||||
/// Return the description. The plugin allocate memory with `libc::malloc` and return the pointer.
|
||||
pub type PluginFuncDesc = fn() -> *const c_char;
|
||||
/// Callback to send message to peer or ui.
|
||||
/// peer, target, id are utf8 strings(null terminated).
|
||||
///
|
||||
/// peer: The peer id.
|
||||
/// target: "peer" or "ui".
|
||||
/// id: The id of this plugin.
|
||||
/// content: The content.
|
||||
/// len: The length of the content.
|
||||
type PluginFuncCallbackMsg = fn(
|
||||
peer: *const c_char,
|
||||
target: *const c_char,
|
||||
id: *const c_char,
|
||||
content: *const c_void,
|
||||
len: usize,
|
||||
);
|
||||
pub type PluginFuncSetCallbackMsg = fn(PluginFuncCallbackMsg);
|
||||
/// The main function of the plugin.
|
||||
/// method: The method. "handle_ui" or "handle_peer"
|
||||
/// args: The arguments.
|
||||
///
|
||||
/// Return null ptr if success.
|
||||
/// Return the error message if failed. `i32-String` without dash, i32 is a signed little-endian number, the String is utf8 string.
|
||||
/// The plugin allocate memory with `libc::malloc` and return the pointer.
|
||||
pub type PluginFuncCall =
|
||||
fn(method: *const c_char, args: *const c_void, len: usize) -> *const c_void;
|
||||
|
||||
macro_rules! make_plugin {
|
||||
($($field:ident : $tp:ty),+) => {
|
||||
pub struct Plugin {
|
||||
_lib: Library,
|
||||
path: String,
|
||||
desc: Option<Desc>,
|
||||
$($field: $tp),+
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
fn new(path: &str) -> ResultType<Self> {
|
||||
let lib = match Library::open(path) {
|
||||
Ok(lib) => lib,
|
||||
Err(e) => {
|
||||
bail!("Failed to load library {}, {}", path, e);
|
||||
}
|
||||
};
|
||||
|
||||
$(let $field = match unsafe { lib.symbol::<$tp>(stringify!($field)) } {
|
||||
Ok(m) => {
|
||||
log::info!("method found {}", stringify!($field));
|
||||
*m
|
||||
},
|
||||
Err(e) => {
|
||||
bail!("Failed to load {} func {}, {}", path, stringify!($field), e);
|
||||
}
|
||||
}
|
||||
;)+
|
||||
|
||||
Ok(Self {
|
||||
_lib: lib,
|
||||
path: path.to_string(),
|
||||
desc: None,
|
||||
$( $field ),+
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
make_plugin!(
|
||||
fn_init: PluginFuncInit,
|
||||
fn_reset: PluginFuncReset,
|
||||
fn_clear: PluginFuncClear,
|
||||
fn_desc: PluginFuncDesc,
|
||||
fn_set_cb_msg: PluginFuncSetCallbackMsg,
|
||||
fn_call: PluginFuncCall
|
||||
);
|
||||
|
||||
pub fn load_plugins<P: AsRef<Path>>(dir: P) -> ResultType<()> {
|
||||
for entry in std::fs::read_dir(dir)? {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
let path = path.to_str().unwrap_or("");
|
||||
if path.ends_with(".so") {
|
||||
if let Err(e) = load_plugin(path) {
|
||||
log::error!("{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read dir entry, {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unload_plugin(id: &str) {
|
||||
if let Some(plugin) = PLUGINS.write().unwrap().remove(id) {
|
||||
let _ret = (plugin.fn_clear)();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reload_plugin(id: &str) -> ResultType<()> {
|
||||
let path = match PLUGINS.read().unwrap().get(id) {
|
||||
Some(plugin) => plugin.path.clone(),
|
||||
None => bail!("Plugin {} not found", id),
|
||||
};
|
||||
unload_plugin(id);
|
||||
load_plugin(&path)
|
||||
}
|
||||
|
||||
pub fn load_plugin(path: &str) -> ResultType<()> {
|
||||
let mut plugin = Plugin::new(path)?;
|
||||
let desc = (plugin.fn_desc)();
|
||||
let desc_res = Desc::from_cstr(desc);
|
||||
unsafe {
|
||||
libc::free(desc as _);
|
||||
}
|
||||
let desc = desc_res?;
|
||||
let id = desc.id().to_string();
|
||||
// to-do validate plugin
|
||||
// to-do check the plugin id (make sure it does not use another plugin's id)
|
||||
(plugin.fn_set_cb_msg)(callback_msg::callback_msg);
|
||||
update_ui_plugin_desc(&desc);
|
||||
update_config(&desc);
|
||||
reload_ui(&desc);
|
||||
plugin.desc = Some(desc);
|
||||
PLUGINS.write().unwrap().insert(id, plugin);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(method: &[u8], id: &str, event: &[u8]) -> ResultType<()> {
|
||||
match PLUGINS.read().unwrap().get(id) {
|
||||
Some(plugin) => {
|
||||
let ret = (plugin.fn_call)(method.as_ptr() as _, event.as_ptr() as _, event.len());
|
||||
if ret.is_null() {
|
||||
Ok(())
|
||||
} else {
|
||||
let (code, msg) = get_code_msg_from_ret(ret);
|
||||
unsafe {
|
||||
libc::free(ret as _);
|
||||
}
|
||||
bail!(
|
||||
"Failed to handle plugin event, id: {}, method: {}, code: {}, msg: {}",
|
||||
id,
|
||||
std::string::String::from_utf8(method.to_vec()).unwrap_or_default(),
|
||||
code,
|
||||
msg
|
||||
);
|
||||
}
|
||||
}
|
||||
None => bail!("Plugin {} not found", id),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn handle_ui_event(id: &str, event: &[u8]) -> ResultType<()> {
|
||||
handle_event(METHOD_HANDLE_UI, id, event)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn handle_server_event(id: &str, event: &[u8]) -> ResultType<()> {
|
||||
handle_event(METHOD_HANDLE_PEER, id, event)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn handle_client_event(id: &str, event: &[u8]) -> Option<Message> {
|
||||
match PLUGINS.read().unwrap().get(id) {
|
||||
Some(plugin) => {
|
||||
let ret = (plugin.fn_call)(
|
||||
METHOD_HANDLE_PEER.as_ptr() as _,
|
||||
event.as_ptr() as _,
|
||||
event.len(),
|
||||
);
|
||||
if ret.is_null() {
|
||||
None
|
||||
} else {
|
||||
let (code, msg) = get_code_msg_from_ret(ret);
|
||||
unsafe {
|
||||
libc::free(ret as _);
|
||||
}
|
||||
if code > ERR_RUSTDESK_HANDLE_BASE && code < ERR_PLUGIN_HANDLE_BASE {
|
||||
let name = plugin.desc.as_ref().unwrap().name();
|
||||
match code {
|
||||
ERR_CALL_NOT_SUPPORTED_METHOD => Some(make_plugin_response(
|
||||
id,
|
||||
name,
|
||||
"plugin method is not supported",
|
||||
)),
|
||||
ERR_CALL_INVALID_ARGS => Some(make_plugin_response(
|
||||
id,
|
||||
name,
|
||||
"plugin arguments is invalid",
|
||||
)),
|
||||
_ => Some(make_plugin_response(id, name, &msg)),
|
||||
}
|
||||
} else {
|
||||
log::error!(
|
||||
"Failed to handle client event, code: {}, msg: {}",
|
||||
code,
|
||||
msg
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Some(make_plugin_response(id, "", "plugin not found")),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_plugin_response(id: &str, name: &str, msg: &str) -> Message {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_plugin_response(PluginResponse {
|
||||
id: id.to_owned(),
|
||||
name: name.to_owned(),
|
||||
msg: msg.to_owned(),
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
msg_out
|
||||
}
|
||||
|
||||
fn update_config(desc: &Desc) {
|
||||
super::config::set_local_items(desc.id(), &desc.config().local);
|
||||
super::config::set_peer_items(desc.id(), &desc.config().peer);
|
||||
}
|
||||
|
||||
fn reload_ui(desc: &Desc) {
|
||||
for (location, ui) in desc.location().ui.iter() {
|
||||
let v: Vec<&str> = location.split('|').collect();
|
||||
// The first element is the "client" or "host".
|
||||
// The second element is the "main", "remote", "cm", "file transfer", "port forward".
|
||||
if v.len() >= 2 {
|
||||
let available_channels = vec![
|
||||
flutter::APP_TYPE_MAIN,
|
||||
flutter::APP_TYPE_DESKTOP_REMOTE,
|
||||
flutter::APP_TYPE_CM,
|
||||
flutter::APP_TYPE_DESKTOP_FILE_TRANSFER,
|
||||
flutter::APP_TYPE_DESKTOP_PORT_FORWARD,
|
||||
];
|
||||
if available_channels.contains(&v[1]) {
|
||||
if let Ok(ui) = serde_json::to_string(&ui) {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("name", "plugin_reload");
|
||||
m.insert("id", desc.id());
|
||||
m.insert("location", &location);
|
||||
m.insert("ui", &ui);
|
||||
flutter::push_global_event(v[1], serde_json::to_string(&m).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_ui_plugin_desc(desc: &Desc) {
|
||||
// This function is rarely used. There's no need to care about serialization efficiency here.
|
||||
if let Ok(desc_str) = serde_json::to_string(desc) {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("name", "plugin_desc");
|
||||
m.insert("desc", &desc_str);
|
||||
flutter::push_global_event(flutter::APP_TYPE_MAIN, serde_json::to_string(&m).unwrap());
|
||||
flutter::push_global_event(
|
||||
flutter::APP_TYPE_DESKTOP_REMOTE,
|
||||
serde_json::to_string(&m).unwrap(),
|
||||
);
|
||||
flutter::push_global_event(flutter::APP_TYPE_CM, serde_json::to_string(&m).unwrap());
|
||||
}
|
||||
}
|
||||
201
src/plugins.rs
Normal file
201
src/plugins.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{c_char, CStr},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use hbb_common::{
|
||||
anyhow::Error,
|
||||
log::{debug, error},
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use libloading::{Library, Symbol};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PLUGIN_REGISTRAR: Arc<PluginRegistar<PluginImpl>> =
|
||||
Arc::new(PluginRegistar::<PluginImpl>::default());
|
||||
}
|
||||
// API needed to be implemented by plugins.
|
||||
pub type PluginInitFunc = fn() -> i32;
|
||||
// API needed to be implemented by plugins.
|
||||
pub type PluginIdFunc = fn() -> *const c_char;
|
||||
// API needed to be implemented by plugins.
|
||||
pub type PluginNameFunc = fn() -> *const c_char;
|
||||
// API needed to be implemented by plugins.
|
||||
pub type PluginDisposeFunc = fn() -> i32;
|
||||
|
||||
pub trait Plugin {
|
||||
// Return: the unique ID which identifies this plugin.
|
||||
fn plugin_id(&self) -> String;
|
||||
// Return: the name which is human-readable.
|
||||
fn plugin_name(&self) -> String;
|
||||
// Return: the virtual table of the plugin.
|
||||
fn plugin_vt(&self) -> &RustDeskPluginTable;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct RustDeskPluginTable {
|
||||
pub init: Option<PluginInitFunc>, // NonNull
|
||||
pub dispose: Option<PluginDisposeFunc>, // NonNull
|
||||
}
|
||||
|
||||
pub struct PluginImpl {
|
||||
vt: RustDeskPluginTable,
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
_inner: Option<Library>,
|
||||
}
|
||||
|
||||
impl Default for PluginImpl {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
_inner: None,
|
||||
vt: Default::default(),
|
||||
id: Default::default(),
|
||||
name: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for PluginImpl {
|
||||
fn plugin_id(&self) -> String {
|
||||
self.id.to_owned()
|
||||
}
|
||||
|
||||
fn plugin_name(&self) -> String {
|
||||
self.name.to_owned()
|
||||
}
|
||||
|
||||
fn plugin_vt(&self) -> &RustDeskPluginTable {
|
||||
&self.vt
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct PluginRegistar<P: Plugin> {
|
||||
plugins: Arc<RwLock<HashMap<String, P>>>,
|
||||
}
|
||||
|
||||
impl<P: Plugin> PluginRegistar<P> {
|
||||
pub fn load_plugin(&self, path: *const c_char) -> i32 {
|
||||
let p = unsafe { CStr::from_ptr(path) };
|
||||
let lib_path = p.to_str().unwrap_or("").to_owned();
|
||||
let lib = unsafe { libloading::Library::new(lib_path.as_str()) };
|
||||
match lib {
|
||||
Ok(lib) => match lib.try_into() {
|
||||
Ok(plugin) => {
|
||||
let plugin: PluginImpl = plugin;
|
||||
// try to initialize this plugin
|
||||
if let Some(init) = plugin.plugin_vt().init {
|
||||
let init_ret = init();
|
||||
if init_ret != 0 {
|
||||
error!(
|
||||
"Error when initializing the plugin {} with error code {}.",
|
||||
plugin.name, init_ret
|
||||
);
|
||||
return init_ret;
|
||||
}
|
||||
}
|
||||
PLUGIN_REGISTRAR
|
||||
.plugins
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(lib_path, plugin);
|
||||
return 0;
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Load plugin failed: {}", err);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("Load plugin failed: {}", err);
|
||||
}
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
pub fn unload_plugin(&self, path: *const c_char) -> i32 {
|
||||
let p = unsafe { CStr::from_ptr(path) };
|
||||
let lib_path = p.to_str().unwrap_or("").to_owned();
|
||||
match PLUGIN_REGISTRAR.plugins.write().unwrap().remove(&lib_path) {
|
||||
Some(plugin) => {
|
||||
if let Some(dispose) = plugin.plugin_vt().dispose {
|
||||
return dispose();
|
||||
}
|
||||
0
|
||||
}
|
||||
None => -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Library> for PluginImpl {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(library: Library) -> Result<Self, Self::Error> {
|
||||
let init: Symbol<PluginInitFunc> = unsafe { library.get(b"plugin_init")? };
|
||||
let dispose: Symbol<PluginDisposeFunc> = unsafe { library.get(b"plugin_dispose")? };
|
||||
let id_func: Symbol<PluginIdFunc> = unsafe { library.get(b"plugin_id")? };
|
||||
let id_string = unsafe {
|
||||
std::ffi::CStr::from_ptr(id_func())
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.to_owned()
|
||||
};
|
||||
let name_func: Symbol<PluginNameFunc> = unsafe { library.get(b"plugin_name")? };
|
||||
let name_string = unsafe {
|
||||
std::ffi::CStr::from_ptr(name_func())
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.to_owned()
|
||||
};
|
||||
debug!(
|
||||
"Successfully loaded the plugin called {} with id {}.",
|
||||
name_string, id_string
|
||||
);
|
||||
Ok(Self {
|
||||
vt: RustDeskPluginTable {
|
||||
init: Some(*init),
|
||||
dispose: Some(*dispose),
|
||||
},
|
||||
id: id_string,
|
||||
name: name_string,
|
||||
_inner: Some(library),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_plugin() {
|
||||
use std::io::Write;
|
||||
let mut cmd = std::process::Command::new("cargo");
|
||||
cmd.current_dir("./examples/custom_plugin");
|
||||
// Strip this shared library.
|
||||
cmd.env("RUSTFLAGS", "-C link-arg=-s");
|
||||
cmd.arg("build");
|
||||
// Spawn the compiler process.
|
||||
let mut child = cmd.spawn().unwrap();
|
||||
// Wait for the compiler to finish.
|
||||
let status = child.wait().unwrap();
|
||||
assert!(status.success());
|
||||
// Load the library.
|
||||
let lib = unsafe {
|
||||
Library::new("./examples/custom_plugin/target/debug/libcustom_plugin.so").unwrap()
|
||||
};
|
||||
let plugin: PluginImpl = lib.try_into().unwrap();
|
||||
assert!(plugin._inner.is_some());
|
||||
assert!(plugin.name == "A Template Rust Plugin");
|
||||
assert!(plugin.id == "TemplatePlugin");
|
||||
println!(
|
||||
"plugin vt size: {}",
|
||||
std::mem::size_of::<RustDeskPluginTable>()
|
||||
);
|
||||
assert!(PLUGIN_REGISTRAR
|
||||
.plugins
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert("test".to_owned(), plugin)
|
||||
.is_none());
|
||||
}
|
||||
@@ -154,7 +154,8 @@ async fn connect_and_login_2(
|
||||
} else {
|
||||
ConnType::PORT_FORWARD
|
||||
};
|
||||
let (mut stream, direct) = Client::start(id, key, token, conn_type, interface.clone()).await?;
|
||||
let (mut stream, direct, _pk) =
|
||||
Client::start(id, key, token, conn_type, interface.clone()).await?;
|
||||
let mut interface = interface;
|
||||
let mut buffer = Vec::new();
|
||||
let mut received = false;
|
||||
@@ -199,8 +200,8 @@ async fn connect_and_login_2(
|
||||
},
|
||||
d = ui_receiver.recv() => {
|
||||
match d {
|
||||
Some(Data::Login((password, remember))) => {
|
||||
interface.handle_login_from_ui(password, remember, &mut stream).await;
|
||||
Some(Data::Login((os_username, os_password, password, remember))) => {
|
||||
interface.handle_login_from_ui(os_username, os_password, password, remember, &mut stream).await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::{
|
||||
use hbb_common::{allow_err, bail, lazy_static, log, tokio, ResultType};
|
||||
use std::{
|
||||
ffi::CString,
|
||||
os::windows::process::CommandExt,
|
||||
sync::Mutex,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@@ -25,18 +24,22 @@ use winapi::{
|
||||
CreateProcessAsUserW, GetCurrentThreadId, QueueUserAPC, ResumeThread,
|
||||
PROCESS_INFORMATION, STARTUPINFOW,
|
||||
},
|
||||
winbase::{
|
||||
WTSGetActiveConsoleSessionId, CREATE_NO_WINDOW, CREATE_SUSPENDED, DETACHED_PROCESS,
|
||||
},
|
||||
winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS},
|
||||
winnt::{MEM_COMMIT, PAGE_READWRITE},
|
||||
winuser::*,
|
||||
},
|
||||
};
|
||||
|
||||
pub const ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe";
|
||||
pub const INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe";
|
||||
pub const WIN_MAG_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe";
|
||||
pub const INJECTED_PROCESS_EXE: &'static str = WIN_MAG_INJECTED_PROCESS_EXE;
|
||||
pub const PRIVACY_WINDOW_NAME: &'static str = "RustDeskPrivacyWindow";
|
||||
|
||||
pub const OCCUPIED: &'static str = "Privacy occupied by another one";
|
||||
pub const TURN_OFF_OTHER_ID: &'static str =
|
||||
"Failed to turn off privacy mode that belongs to someone else";
|
||||
pub const NO_DISPLAYS: &'static str = "No displays";
|
||||
|
||||
pub const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT: u32 = 2;
|
||||
pub const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 4;
|
||||
|
||||
@@ -317,14 +320,6 @@ fn wait_find_privacy_hwnd(msecs: u128) -> ResultType<HWND> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_process_consent_running() -> ResultType<bool> {
|
||||
let output = std::process::Command::new("cmd")
|
||||
.args(&["/C", "tasklist | findstr consent.exe"])
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output()?;
|
||||
Ok(output.status.success() && !output.stdout.is_empty())
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn set_privacy_mode_state(
|
||||
conn_id: i32,
|
||||
38
src/rc.rs
38
src/rc.rs
@@ -1,38 +0,0 @@
|
||||
use hbb_common::{bail, ResultType};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::prelude::*,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn extract_resources(root_path: &str) -> ResultType<()> {
|
||||
let mut resources: Vec<(&str, &[u8])> = Vec::new();
|
||||
resources.push((outfile_4, &outdata_4));
|
||||
do_extract(root_path, resources)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn do_extract(root_path: &str, resources: Vec<(&str, &[u8])>) -> ResultType<()> {
|
||||
let mut root_path = root_path.replace("\\", "/");
|
||||
if !root_path.ends_with('/') {
|
||||
root_path.push('/');
|
||||
}
|
||||
let root_path = Path::new(&root_path);
|
||||
for (outfile, data) in resources {
|
||||
let outfile_path = root_path.join(outfile);
|
||||
match outfile_path.parent().and_then(|p| p.to_str()) {
|
||||
None => {
|
||||
bail!("Failed to get parent of {}", outfile_path.display());
|
||||
}
|
||||
Some(p) => {
|
||||
fs::create_dir_all(p)?;
|
||||
let mut of = File::create(outfile_path)?;
|
||||
of.write_all(data)?;
|
||||
of.flush()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -72,6 +72,9 @@ impl RendezvousMediator {
|
||||
allow_err!(super::lan::start_listening());
|
||||
});
|
||||
}
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
crate::platform::linux_desktop_manager::start_xdesktop();
|
||||
loop {
|
||||
Config::reset_online();
|
||||
if Config::get_option("stop-service").is_empty() {
|
||||
@@ -96,6 +99,11 @@ impl RendezvousMediator {
|
||||
}
|
||||
sleep(1.).await;
|
||||
}
|
||||
// It should be better to call stop_xdesktop.
|
||||
// But for server, it also is Ok without calling this method.
|
||||
// #[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
// #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
// crate::platform::linux_desktop_manager::stop_xdesktop();
|
||||
}
|
||||
|
||||
pub async fn start(server: ServerPtr, host: String) -> ResultType<()> {
|
||||
|
||||
@@ -365,7 +365,7 @@ pub fn check_zombie() {
|
||||
/// Otherwise, client will check if there's already a server and start one if not.
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
#[tokio::main]
|
||||
pub async fn start_server(is_server: bool) {
|
||||
pub async fn start_server(_is_server: bool) {
|
||||
crate::RendezvousMediator::start_all().await;
|
||||
}
|
||||
|
||||
@@ -389,7 +389,7 @@ pub async fn start_server(is_server: bool) {
|
||||
use std::sync::Once;
|
||||
static ONCE: Once = Once::new();
|
||||
ONCE.call_once(|| {
|
||||
scrap::hwcodec::check_config_process(false);
|
||||
scrap::hwcodec::check_config_process();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -451,17 +451,13 @@ pub async fn start_ipc_url_server() {
|
||||
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!");
|
||||
let mut m = HashMap::new();
|
||||
m.insert("name", "on_url_scheme_received");
|
||||
m.insert("url", url.as_str());
|
||||
let event = serde_json::to_string(&m).unwrap();
|
||||
match crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, event) {
|
||||
None => log::warn!("No main window app found!"),
|
||||
Some(..) => {}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
// https://github.com/krruzic/pulsectl
|
||||
|
||||
use super::*;
|
||||
use hbb_common::get_time;
|
||||
use magnum_opus::{Application::*, Channels::*, Encoder};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
@@ -65,6 +64,7 @@ mod pa_impl {
|
||||
)))
|
||||
.await
|
||||
);
|
||||
#[cfg(target_os = "linux")]
|
||||
let zero_audio_frame: Vec<f32> = vec![0.; AUDIO_DATA_SIZE_U8 / 4];
|
||||
while sp.ok() && !RESTARTING.load(Ordering::SeqCst) {
|
||||
sp.snapshot(|sps| {
|
||||
@@ -104,11 +104,12 @@ mod cpal_impl {
|
||||
use super::*;
|
||||
use cpal::{
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
Device, Host, SupportedStreamConfig,
|
||||
BufferSize, Device, Host, InputCallbackInfo, StreamConfig, SupportedStreamConfig,
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref HOST: Host = cpal::default_host();
|
||||
static ref INPUT_BUFFER: Arc<Mutex<std::collections::VecDeque<f32>>> = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -213,12 +214,9 @@ mod cpal_impl {
|
||||
}
|
||||
|
||||
fn play(sp: &GenericService) -> ResultType<(Box<dyn StreamTrait>, Arc<Message>)> {
|
||||
use cpal::SampleFormat::*;
|
||||
let (device, config) = get_device()?;
|
||||
let sp = sp.clone();
|
||||
let err_fn = move |err| {
|
||||
// too many UnknownErrno, will improve later
|
||||
log::trace!("an error occurred on stream: {}", err);
|
||||
};
|
||||
// Sample rate must be one of 8000, 12000, 16000, 24000, or 48000.
|
||||
let sample_rate_0 = config.sample_rate().0;
|
||||
let sample_rate = if sample_rate_0 < 12000 {
|
||||
@@ -232,6 +230,40 @@ mod cpal_impl {
|
||||
} else {
|
||||
48000
|
||||
};
|
||||
let stream = match config.sample_format() {
|
||||
I8 => build_input_stream::<i8>(device, &config, sp, sample_rate)?,
|
||||
I16 => build_input_stream::<i16>(device, &config, sp, sample_rate)?,
|
||||
I32 => build_input_stream::<i32>(device, &config, sp, sample_rate)?,
|
||||
I64 => build_input_stream::<i64>(device, &config, sp, sample_rate)?,
|
||||
U8 => build_input_stream::<u8>(device, &config, sp, sample_rate)?,
|
||||
U16 => build_input_stream::<u16>(device, &config, sp, sample_rate)?,
|
||||
U32 => build_input_stream::<u32>(device, &config, sp, sample_rate)?,
|
||||
U64 => build_input_stream::<u64>(device, &config, sp, sample_rate)?,
|
||||
F32 => build_input_stream::<f32>(device, &config, sp, sample_rate)?,
|
||||
F64 => build_input_stream::<f64>(device, &config, sp, sample_rate)?,
|
||||
f => bail!("unsupported audio format: {:?}", f),
|
||||
};
|
||||
stream.play()?;
|
||||
Ok((
|
||||
Box::new(stream),
|
||||
Arc::new(create_format_msg(sample_rate, config.channels())),
|
||||
))
|
||||
}
|
||||
|
||||
fn build_input_stream<T>(
|
||||
device: cpal::Device,
|
||||
config: &cpal::SupportedStreamConfig,
|
||||
sp: GenericService,
|
||||
sample_rate: u32,
|
||||
) -> ResultType<cpal::Stream>
|
||||
where
|
||||
T: cpal::SizedSample + dasp::sample::ToSample<f32>,
|
||||
{
|
||||
let err_fn = move |err| {
|
||||
// too many UnknownErrno, will improve later
|
||||
log::trace!("an error occurred on stream: {}", err);
|
||||
};
|
||||
let sample_rate_0 = config.sample_rate().0;
|
||||
log::debug!("Audio sample rate : {}", sample_rate);
|
||||
unsafe {
|
||||
AUDIO_ZERO_COUNT = 0;
|
||||
@@ -242,57 +274,38 @@ mod cpal_impl {
|
||||
LowDelay,
|
||||
)?;
|
||||
let channels = config.channels();
|
||||
let stream = match config.sample_format() {
|
||||
cpal::SampleFormat::F32 => device.build_input_stream(
|
||||
&config.into(),
|
||||
move |data, _: &_| {
|
||||
send(
|
||||
data,
|
||||
sample_rate_0,
|
||||
sample_rate,
|
||||
channels,
|
||||
&mut encoder,
|
||||
&sp,
|
||||
);
|
||||
},
|
||||
err_fn,
|
||||
)?,
|
||||
cpal::SampleFormat::I16 => device.build_input_stream(
|
||||
&config.into(),
|
||||
move |data: &[i16], _: &_| {
|
||||
let buffer: Vec<_> = data.iter().map(|s| cpal::Sample::to_f32(s)).collect();
|
||||
send(
|
||||
&buffer,
|
||||
sample_rate_0,
|
||||
sample_rate,
|
||||
channels,
|
||||
&mut encoder,
|
||||
&sp,
|
||||
);
|
||||
},
|
||||
err_fn,
|
||||
)?,
|
||||
cpal::SampleFormat::U16 => device.build_input_stream(
|
||||
&config.into(),
|
||||
move |data: &[u16], _: &_| {
|
||||
let buffer: Vec<_> = data.iter().map(|s| cpal::Sample::to_f32(s)).collect();
|
||||
send(
|
||||
&buffer,
|
||||
sample_rate_0,
|
||||
sample_rate,
|
||||
channels,
|
||||
&mut encoder,
|
||||
&sp,
|
||||
);
|
||||
},
|
||||
err_fn,
|
||||
)?,
|
||||
// https://www.opus-codec.org/docs/html_api/group__opusencoder.html#gace941e4ef26ed844879fde342ffbe546
|
||||
// https://chromium.googlesource.com/chromium/deps/opus/+/1.1.1/include/opus.h
|
||||
let encode_len = sample_rate as usize * channels as usize / 100; // 10 ms
|
||||
INPUT_BUFFER.lock().unwrap().clear();
|
||||
let timeout = None;
|
||||
let stream_config = StreamConfig {
|
||||
channels,
|
||||
sample_rate: config.sample_rate(),
|
||||
buffer_size: BufferSize::Default,
|
||||
};
|
||||
stream.play()?;
|
||||
Ok((
|
||||
Box::new(stream),
|
||||
Arc::new(create_format_msg(sample_rate, channels)),
|
||||
))
|
||||
let stream = device.build_input_stream(
|
||||
&stream_config,
|
||||
move |data: &[T], _: &InputCallbackInfo| {
|
||||
let buffer: Vec<f32> = data.iter().map(|s| T::to_sample(*s)).collect();
|
||||
let mut lock = INPUT_BUFFER.lock().unwrap();
|
||||
lock.extend(buffer);
|
||||
while lock.len() >= encode_len {
|
||||
let frame: Vec<f32> = lock.drain(0..encode_len).collect();
|
||||
send(
|
||||
&frame,
|
||||
sample_rate_0,
|
||||
sample_rate,
|
||||
channels,
|
||||
&mut encoder,
|
||||
&sp,
|
||||
);
|
||||
}
|
||||
},
|
||||
err_fn,
|
||||
timeout,
|
||||
)?;
|
||||
Ok(stream)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +362,6 @@ fn send_f32(data: &[f32], encoder: &mut Encoder, sp: &GenericService) {
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_audio_frame(AudioFrame {
|
||||
data: data.into(),
|
||||
timestamp: get_time(),
|
||||
..Default::default()
|
||||
});
|
||||
sp.send(msg_out);
|
||||
@@ -369,7 +381,6 @@ fn send_f32(data: &[f32], encoder: &mut Encoder, sp: &GenericService) {
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_audio_frame(AudioFrame {
|
||||
data: data.into(),
|
||||
timestamp: get_time(),
|
||||
..Default::default()
|
||||
});
|
||||
sp.send(msg_out);
|
||||
|
||||
@@ -3,12 +3,14 @@ use super::{input_service::*, *};
|
||||
use crate::clipboard_file::*;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::common::update_clipboard;
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
use crate::platform::linux_desktop_manager;
|
||||
#[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,
|
||||
new_voice_call_request, new_voice_call_response, start_audio_thread, MediaData, MediaSender,
|
||||
},
|
||||
common::{get_default_sound_input, set_sound_input},
|
||||
video_service,
|
||||
@@ -17,6 +19,9 @@ use crate::{
|
||||
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
|
||||
use crate::{ipc, VERSION};
|
||||
use cidr_utils::cidr::IpCidr;
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
use hbb_common::platform::linux::run_cmds;
|
||||
use hbb_common::{
|
||||
config::Config,
|
||||
fs,
|
||||
@@ -46,6 +51,9 @@ use std::{
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use system_shutdown;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -65,7 +73,9 @@ pub struct ConnInner {
|
||||
}
|
||||
|
||||
enum MessageInput {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Mouse((MouseEvent, i32)),
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Key((KeyEvent, bool)),
|
||||
BlockOn,
|
||||
BlockOff,
|
||||
@@ -126,10 +136,19 @@ pub struct Connection {
|
||||
#[cfg(windows)]
|
||||
portable: PortableState,
|
||||
from_switch: bool,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
origin_resolution: HashMap<String, Resolution>,
|
||||
voice_call_request_timestamp: Option<NonZeroI64>,
|
||||
audio_input_device_before_voice_call: Option<String>,
|
||||
options_in_login: Option<OptionMessage>,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pressed_modifiers: HashSet<rdev::Key>,
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
rx_cm_stream_ready: mpsc::Receiver<()>,
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
tx_desktop_ready: mpsc::Sender<()>,
|
||||
}
|
||||
|
||||
impl ConnInner {
|
||||
@@ -188,9 +207,14 @@ impl Connection {
|
||||
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_input, _rx_input) = std_mpsc::channel();
|
||||
let mut hbbs_rx = crate::hbbs_http::sync::signal_receiver();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let (tx_cm_stream_ready, _rx_cm_stream_ready) = mpsc::channel(1);
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let (_tx_desktop_ready, rx_desktop_ready) = mpsc::channel(1);
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let tx_cloned = tx.clone();
|
||||
let mut conn = Self {
|
||||
inner: ConnInner {
|
||||
@@ -234,15 +258,26 @@ impl Connection {
|
||||
#[cfg(windows)]
|
||||
portable: Default::default(),
|
||||
from_switch: false,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
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")))]
|
||||
pressed_modifiers: Default::default(),
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
rx_cm_stream_ready: _rx_cm_stream_ready,
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
tx_desktop_ready: _tx_desktop_ready,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = start_ipc(rx_to_cm, tx_from_cm).await {
|
||||
if let Err(err) =
|
||||
start_ipc(rx_to_cm, tx_from_cm, rx_desktop_ready, tx_cm_stream_ready).await
|
||||
{
|
||||
log::error!("ipc to connection manager exit: {}", err);
|
||||
}
|
||||
});
|
||||
@@ -283,7 +318,7 @@ impl Connection {
|
||||
);
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
std::thread::spawn(move || Self::handle_input(rx_input, tx_cloned));
|
||||
std::thread::spawn(move || Self::handle_input(_rx_input, tx_cloned));
|
||||
let mut second_timer = time::interval(Duration::from_secs(1));
|
||||
|
||||
loop {
|
||||
@@ -529,7 +564,7 @@ impl Connection {
|
||||
let _ = privacy_mode::turn_off_privacy(0);
|
||||
}
|
||||
video_service::notify_video_frame_fetched(id, None);
|
||||
scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove);
|
||||
scrap::codec::Encoder::update(id, scrap::codec::EncodingUpdate::Remove);
|
||||
video_service::VIDEO_QOS.lock().unwrap().reset();
|
||||
if conn.authorized {
|
||||
password::update_temporary_password();
|
||||
@@ -843,17 +878,25 @@ impl Connection {
|
||||
pi.hostname = DEVICE_NAME.lock().unwrap().clone();
|
||||
pi.platform = "Android".into();
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let (h264, h265) = scrap::codec::Encoder::supported_encoding();
|
||||
pi.encoding = Some(SupportedEncoding {
|
||||
h264,
|
||||
h265,
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
let mut platform_additions = serde_json::Map::new();
|
||||
if crate::platform::current_is_wayland() {
|
||||
platform_additions.insert("is_wayland".into(), json!(true));
|
||||
}
|
||||
#[cfg(feature = "linux_headless")]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
if linux_desktop_manager::is_headless() {
|
||||
platform_additions.insert("headless".into(), json!(true));
|
||||
}
|
||||
if !platform_additions.is_empty() {
|
||||
pi.platform_additions =
|
||||
serde_json::to_string(&platform_additions).unwrap_or("".into());
|
||||
}
|
||||
}
|
||||
|
||||
pi.encoding = Some(scrap::codec::Encoder::supported_encoding()).into();
|
||||
|
||||
if self.port_forward_socket.is_some() {
|
||||
let mut msg_out = Message::new();
|
||||
res.set_peer_info(pi);
|
||||
@@ -864,9 +907,11 @@ impl Connection {
|
||||
#[cfg(target_os = "linux")]
|
||||
if !self.file_transfer.is_some() && !self.port_forward_socket.is_some() {
|
||||
let dtype = crate::platform::linux::get_display_server();
|
||||
if dtype != "x11" && dtype != "wayland" {
|
||||
if dtype != crate::platform::linux::DISPLAY_SERVER_X11
|
||||
&& dtype != crate::platform::linux::DISPLAY_SERVER_WAYLAND
|
||||
{
|
||||
res.set_error(format!(
|
||||
"Unsupported display server type {}, x11 or wayland expected",
|
||||
"Unsupported display server type \"{}\", x11 or wayland expected",
|
||||
dtype
|
||||
));
|
||||
let mut msg_out = Message::new();
|
||||
@@ -1031,11 +1076,13 @@ impl Connection {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn input_mouse(&self, msg: MouseEvent, conn_id: i32) {
|
||||
self.tx_input.send(MessageInput::Mouse((msg, conn_id))).ok();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn input_key(&self, msg: KeyEvent, press: bool) {
|
||||
self.tx_input.send(MessageInput::Key((msg, press))).ok();
|
||||
}
|
||||
@@ -1126,21 +1173,21 @@ impl Connection {
|
||||
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(
|
||||
if let Some(q) = o.supported_decoding.clone().take() {
|
||||
scrap::codec::Encoder::update(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::State(q),
|
||||
scrap::codec::EncodingUpdate::New(q),
|
||||
);
|
||||
} else {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
scrap::codec::Encoder::update(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||
scrap::codec::EncodingUpdate::NewOnlyVP9,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
scrap::codec::Encoder::update(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||
scrap::codec::EncodingUpdate::NewOnlyVP9,
|
||||
);
|
||||
}
|
||||
self.video_ack_required = lr.video_ack_required;
|
||||
@@ -1204,8 +1251,35 @@ impl Connection {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
let desktop_err = match lr.os_login.as_ref() {
|
||||
Some(os_login) => {
|
||||
linux_desktop_manager::try_start_desktop(&os_login.username, &os_login.password)
|
||||
}
|
||||
None => linux_desktop_manager::try_start_desktop("", ""),
|
||||
};
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
let is_headless = linux_desktop_manager::is_headless();
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
let wait_ipc_timeout = 10_000;
|
||||
|
||||
// If err is LOGIN_MSG_DESKTOP_SESSION_NOT_READY, just keep this msg and go on checking password.
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
if !desktop_err.is_empty()
|
||||
&& desktop_err != crate::client::LOGIN_MSG_DESKTOP_SESSION_NOT_READY
|
||||
{
|
||||
self.send_login_error(desktop_err).await;
|
||||
return true;
|
||||
}
|
||||
|
||||
if !hbb_common::is_ipv4_str(&lr.username) && lr.username != Config::get_id() {
|
||||
self.send_login_error("Offline").await;
|
||||
self.send_login_error(crate::client::LOGIN_MSG_OFFLINE)
|
||||
.await;
|
||||
} else if password::approve_mode() == ApproveMode::Click
|
||||
|| password::approve_mode() == ApproveMode::Both && !password::has_valid_password()
|
||||
{
|
||||
@@ -1213,7 +1287,8 @@ impl Connection {
|
||||
if hbb_common::get_version_number(&lr.version)
|
||||
>= hbb_common::get_version_number("1.2.0")
|
||||
{
|
||||
self.send_login_error("No Password Access").await;
|
||||
self.send_login_error(crate::client::LOGIN_MSG_NO_PASSWORD_ACCESS)
|
||||
.await;
|
||||
}
|
||||
return true;
|
||||
} else if password::approve_mode() == ApproveMode::Password
|
||||
@@ -1222,12 +1297,42 @@ impl Connection {
|
||||
self.send_login_error("Connection not allowed").await;
|
||||
return false;
|
||||
} 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() {
|
||||
return false;
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
if desktop_err.is_empty() {
|
||||
#[cfg(target_os = "linux")]
|
||||
if is_headless {
|
||||
self.tx_desktop_ready.send(()).await.ok();
|
||||
let _res = timeout(wait_ipc_timeout, self.rx_cm_stream_ready.recv()).await;
|
||||
}
|
||||
self.try_start_cm(lr.my_id, lr.my_name, true);
|
||||
self.send_logon_response().await;
|
||||
if self.port_forward_socket.is_some() {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
self.send_login_error(desktop_err).await;
|
||||
}
|
||||
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
|
||||
{
|
||||
self.try_start_cm(lr.my_id, lr.my_name, true);
|
||||
self.send_logon_response().await;
|
||||
if self.port_forward_socket.is_some() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if lr.password.is_empty() {
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
if desktop_err.is_empty() {
|
||||
self.try_start_cm(lr.my_id, lr.my_name, false);
|
||||
} else {
|
||||
self.send_login_error(
|
||||
crate::client::LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_EMPTY,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
|
||||
self.try_start_cm(lr.my_id, lr.my_name, false);
|
||||
} else {
|
||||
let mut failure = LOGIN_FAILURES
|
||||
@@ -1269,16 +1374,52 @@ impl Connection {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.ip.clone(), failure);
|
||||
self.send_login_error("Wrong Password").await;
|
||||
self.try_start_cm(lr.my_id, lr.my_name, false);
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
if desktop_err.is_empty() {
|
||||
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
|
||||
.await;
|
||||
self.try_start_cm(lr.my_id, lr.my_name, false);
|
||||
} else {
|
||||
self.send_login_error(
|
||||
crate::client::LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
|
||||
{
|
||||
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
|
||||
.await;
|
||||
self.try_start_cm(lr.my_id, lr.my_name, false);
|
||||
}
|
||||
} else {
|
||||
if failure.0 != 0 {
|
||||
LOGIN_FAILURES.lock().unwrap().remove(&self.ip);
|
||||
}
|
||||
self.try_start_cm(lr.my_id, lr.my_name, true);
|
||||
self.send_logon_response().await;
|
||||
if self.port_forward_socket.is_some() {
|
||||
return false;
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
if desktop_err.is_empty() {
|
||||
#[cfg(target_os = "linux")]
|
||||
if is_headless {
|
||||
self.tx_desktop_ready.send(()).await.ok();
|
||||
let _res =
|
||||
timeout(wait_ipc_timeout, self.rx_cm_stream_ready.recv()).await;
|
||||
}
|
||||
self.send_logon_response().await;
|
||||
self.try_start_cm(lr.my_id, lr.my_name, true);
|
||||
if self.port_forward_socket.is_some() {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
self.send_login_error(desktop_err).await;
|
||||
}
|
||||
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
|
||||
{
|
||||
self.send_logon_response().await;
|
||||
self.try_start_cm(lr.my_id, lr.my_name, true);
|
||||
if self.port_forward_socket.is_some() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1331,8 +1472,10 @@ impl Connection {
|
||||
self.input_mouse(me, self.inner.id());
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
Some(message::Union::KeyEvent(..)) => {}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(message::Union::KeyEvent(me)) => {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if self.peer_keyboard_enabled() {
|
||||
if is_enter(&me) {
|
||||
CLICK_TIME.store(get_time(), Ordering::SeqCst);
|
||||
@@ -1345,6 +1488,30 @@ impl Connection {
|
||||
} else {
|
||||
me.press
|
||||
};
|
||||
|
||||
let key = match me.mode.enum_value_or_default() {
|
||||
KeyboardMode::Map => {
|
||||
Some(crate::keyboard::keycode_to_rdev_key(me.chr()))
|
||||
}
|
||||
KeyboardMode::Translate => {
|
||||
if let Some(key_event::Union::Chr(code)) = me.union {
|
||||
Some(crate::keyboard::keycode_to_rdev_key(code & 0x0000FFFF))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
.filter(crate::keyboard::is_modifier);
|
||||
|
||||
if let Some(key) = key {
|
||||
if is_press {
|
||||
self.pressed_modifiers.insert(key);
|
||||
} else {
|
||||
self.pressed_modifiers.remove(&key);
|
||||
}
|
||||
}
|
||||
|
||||
if is_press {
|
||||
match me.union {
|
||||
Some(key_event::Union::Unicode(_))
|
||||
@@ -1360,11 +1527,11 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::Clipboard(cb)) =>
|
||||
Some(message::Union::Clipboard(_cb)) =>
|
||||
{
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if self.clipboard {
|
||||
update_clipboard(cb, None);
|
||||
update_clipboard(_cb, None);
|
||||
}
|
||||
}
|
||||
Some(message::Union::Cliprdr(_clip)) => {
|
||||
@@ -1601,11 +1768,7 @@ impl Connection {
|
||||
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)));
|
||||
self.audio_sender = Some(start_audio_thread());
|
||||
allow_err!(self
|
||||
.audio_sender
|
||||
.as_ref()
|
||||
@@ -1647,12 +1810,19 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(misc::Union::PluginRequest(p)) => {
|
||||
if let Some(msg) = crate::plugin::handle_client_event(&p.id, &p.content) {
|
||||
self.send(msg).await;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::AudioFrame(frame)) => {
|
||||
if !self.disable_audio {
|
||||
if let Some(sender) = &self.audio_sender {
|
||||
allow_err!(sender.send(MediaData::AudioFrame(frame)));
|
||||
allow_err!(sender.send(MediaData::AudioFrame(Box::new(frame))));
|
||||
} else {
|
||||
log::warn!(
|
||||
"Processing audio frame without the voice call audio sender."
|
||||
@@ -1741,11 +1911,8 @@ 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 Some(q) = o.supported_decoding.clone().take() {
|
||||
scrap::codec::Encoder::update(self.inner.id(), scrap::codec::EncodingUpdate::New(q));
|
||||
}
|
||||
if let Ok(q) = o.lock_after_session_end.enum_value() {
|
||||
if q != BoolOption::NotSet {
|
||||
@@ -2006,6 +2173,14 @@ impl Connection {
|
||||
})
|
||||
.count();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn release_pressed_modifiers(&mut self) {
|
||||
for modifier in self.pressed_modifiers.iter() {
|
||||
rdev::simulate(&rdev::EventType::KeyRelease(*modifier)).ok();
|
||||
}
|
||||
self.pressed_modifiers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
|
||||
@@ -2019,6 +2194,8 @@ pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
|
||||
async fn start_ipc(
|
||||
mut rx_to_cm: mpsc::UnboundedReceiver<ipc::Data>,
|
||||
tx_from_cm: mpsc::UnboundedSender<ipc::Data>,
|
||||
mut _rx_desktop_ready: mpsc::Receiver<()>,
|
||||
tx_stream_ready: mpsc::Sender<()>,
|
||||
) -> ResultType<()> {
|
||||
loop {
|
||||
if !crate::platform::is_prelogin() {
|
||||
@@ -2034,6 +2211,39 @@ async fn start_ipc(
|
||||
if password::hide_cm() {
|
||||
args.push("--hide");
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(not(feature = "linux_headless"))]
|
||||
let user = None;
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(any(feature = "flatpak", feature = "appimage"))]
|
||||
let user = None;
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
let mut user = None;
|
||||
// Cm run as user, wait until desktop session is ready.
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
if linux_desktop_manager::is_headless() {
|
||||
let mut username = linux_desktop_manager::get_username();
|
||||
loop {
|
||||
if !username.is_empty() {
|
||||
break;
|
||||
}
|
||||
let _res = timeout(1_000, _rx_desktop_ready.recv()).await;
|
||||
username = linux_desktop_manager::get_username();
|
||||
}
|
||||
let uid = {
|
||||
let output = run_cmds(&format!("id -u {}", &username))?;
|
||||
let output = output.trim();
|
||||
if output.is_empty() || !output.parse::<i32>().is_ok() {
|
||||
bail!("Invalid username {}", &username);
|
||||
}
|
||||
output.to_string()
|
||||
};
|
||||
user = Some((uid, username));
|
||||
args = vec!["--cm-no-ui"];
|
||||
}
|
||||
let run_done;
|
||||
if crate::platform::is_root() {
|
||||
let mut res = Ok(None);
|
||||
@@ -2046,7 +2256,7 @@ async fn start_ipc(
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
log::debug!("Start cm");
|
||||
res = crate::platform::run_as_user(args.clone(), None);
|
||||
res = crate::platform::run_as_user(args.clone(), user.clone());
|
||||
}
|
||||
if res.is_ok() {
|
||||
break;
|
||||
@@ -2078,6 +2288,8 @@ async fn start_ipc(
|
||||
bail!("Failed to connect to connection manager");
|
||||
}
|
||||
}
|
||||
|
||||
let _res = tx_stream_ready.send(()).await;
|
||||
let mut stream = stream.unwrap();
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -2135,13 +2347,13 @@ fn try_activate_screen() {
|
||||
|
||||
mod privacy_mode {
|
||||
use super::*;
|
||||
#[cfg(windows)]
|
||||
use crate::privacy_win_mag;
|
||||
|
||||
pub(super) fn turn_off_privacy(_conn_id: i32) -> Message {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use crate::win_privacy::*;
|
||||
|
||||
let res = turn_off_privacy(_conn_id, None);
|
||||
let res = privacy_win_mag::turn_off_privacy(_conn_id, None);
|
||||
match res {
|
||||
Ok(_) => crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::PrvOffSucceeded,
|
||||
@@ -2163,7 +2375,7 @@ mod privacy_mode {
|
||||
pub(super) fn turn_on_privacy(_conn_id: i32) -> ResultType<bool> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let plugin_exist = crate::win_privacy::turn_on_privacy(_conn_id)?;
|
||||
let plugin_exist = privacy_win_mag::turn_on_privacy(_conn_id)?;
|
||||
Ok(plugin_exist)
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
@@ -2203,3 +2415,10 @@ impl Default for PortableState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Connection {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
self.release_pressed_modifiers();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,22 +71,18 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) {
|
||||
move |_, _, (_uni_links,): (String,)| {
|
||||
#[cfg(feature = "flutter")]
|
||||
{
|
||||
use crate::flutter::{self, APP_TYPE_MAIN};
|
||||
|
||||
if let Some(stream) = flutter::GLOBAL_EVENT_STREAM
|
||||
.write()
|
||||
.unwrap()
|
||||
.get(APP_TYPE_MAIN)
|
||||
{
|
||||
let data = HashMap::from([
|
||||
("name", "new_connection"),
|
||||
("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.");
|
||||
use crate::flutter;
|
||||
let data = HashMap::from([
|
||||
("name", "new_connection"),
|
||||
("uni_links", _uni_links.as_str()),
|
||||
]);
|
||||
let event = serde_json::ser::to_string(&data).unwrap_or("".to_string());
|
||||
match crate::flutter::push_global_event(flutter::APP_TYPE_MAIN, event) {
|
||||
None => log::error!("failed to find main event stream"),
|
||||
Some(false) => {
|
||||
log::error!("failed to add dbus message to flutter global dbus stream.")
|
||||
}
|
||||
} else {
|
||||
log::error!("failed to find main event stream");
|
||||
Some(true) => {}
|
||||
}
|
||||
}
|
||||
return Ok((DBUS_METHOD_RETURN_SUCCESS.to_string(),));
|
||||
|
||||
@@ -5,16 +5,15 @@ use crate::common::IS_X11;
|
||||
use dispatch::Queue;
|
||||
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
use hbb_common::{config::COMPRESS_LEVEL, get_time, protobuf::EnumOrUnknown};
|
||||
use rdev::{self, EventType, Key as RdevKey, RawKey};
|
||||
use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey};
|
||||
#[cfg(target_os = "macos")]
|
||||
use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput};
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
ops::Sub,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
thread,
|
||||
time::{self, Instant},
|
||||
time::{self, Duration, Instant},
|
||||
};
|
||||
|
||||
const INVALID_CURSOR_POS: i32 = i32::MIN;
|
||||
@@ -113,6 +112,119 @@ impl Subscriber for MouseCursorSub {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
struct LockModesHandler {
|
||||
caps_lock_changed: bool,
|
||||
num_lock_changed: bool,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
struct LockModesHandler;
|
||||
|
||||
impl LockModesHandler {
|
||||
#[inline]
|
||||
fn is_modifier_enabled(key_event: &KeyEvent, modifier: ControlKey) -> bool {
|
||||
key_event.modifiers.contains(&modifier.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
fn new_handler(key_event: &KeyEvent, _is_numpad_key: bool) -> Self {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
{
|
||||
Self::new(key_event, _is_numpad_key)
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
Self::new(key_event)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
fn new(key_event: &KeyEvent, is_numpad_key: bool) -> Self {
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
let event_caps_enabled = Self::is_modifier_enabled(key_event, ControlKey::CapsLock);
|
||||
let local_caps_enabled = en.get_key_state(enigo::Key::CapsLock);
|
||||
let caps_lock_changed = event_caps_enabled != local_caps_enabled;
|
||||
if caps_lock_changed {
|
||||
en.key_click(enigo::Key::CapsLock);
|
||||
}
|
||||
|
||||
let mut num_lock_changed = false;
|
||||
if is_numpad_key {
|
||||
let local_num_enabled = en.get_key_state(enigo::Key::NumLock);
|
||||
let event_num_enabled = Self::is_modifier_enabled(key_event, ControlKey::NumLock);
|
||||
num_lock_changed = event_num_enabled != local_num_enabled;
|
||||
} else if is_legacy_mode(key_event) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
num_lock_changed =
|
||||
should_disable_numlock(key_event) && en.get_key_state(enigo::Key::NumLock);
|
||||
}
|
||||
}
|
||||
if num_lock_changed {
|
||||
en.key_click(enigo::Key::NumLock);
|
||||
}
|
||||
|
||||
Self {
|
||||
caps_lock_changed,
|
||||
num_lock_changed,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn new(key_event: &KeyEvent) -> Self {
|
||||
let event_caps_enabled = Self::is_modifier_enabled(key_event, ControlKey::CapsLock);
|
||||
// Do not use the following code to detect `local_caps_enabled`.
|
||||
// Because the state of get_key_state will not affect simuation of `VIRTUAL_INPUT_STATE` in this file.
|
||||
//
|
||||
// let local_caps_enabled = VirtualInput::get_key_state(
|
||||
// CGEventSourceStateID::CombinedSessionState,
|
||||
// rdev::kVK_CapsLock,
|
||||
// );
|
||||
let local_caps_enabled = unsafe {
|
||||
let _lock = VIRTUAL_INPUT_MTX.lock();
|
||||
VIRTUAL_INPUT_STATE
|
||||
.as_ref()
|
||||
.map_or(false, |input| input.capslock_down)
|
||||
};
|
||||
if event_caps_enabled && !local_caps_enabled {
|
||||
press_capslock();
|
||||
} else if !event_caps_enabled && local_caps_enabled {
|
||||
release_capslock();
|
||||
}
|
||||
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
impl Drop for LockModesHandler {
|
||||
fn drop(&mut self) {
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
if self.caps_lock_changed {
|
||||
en.key_click(enigo::Key::CapsLock);
|
||||
}
|
||||
if self.num_lock_changed {
|
||||
en.key_click(enigo::Key::NumLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn should_disable_numlock(evt: &KeyEvent) -> bool {
|
||||
// disable numlock if press home etc when numlock is on,
|
||||
// because we will get numpad value (7,8,9 etc) if not
|
||||
match (&evt.union, evt.mode.enum_value_or(KeyboardMode::Legacy)) {
|
||||
(Some(key_event::Union::ControlKey(ck)), KeyboardMode::Legacy) => {
|
||||
return NUMPAD_KEY_MAP.contains_key(&ck.value());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub const NAME_CURSOR: &'static str = "mouse_cursor";
|
||||
pub const NAME_POS: &'static str = "mouse_pos";
|
||||
pub type MouseCursorService = ServiceTmpl<MouseCursorSub>;
|
||||
@@ -268,10 +380,33 @@ lazy_static::lazy_static! {
|
||||
static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
struct VirtualInputState {
|
||||
virtual_input: VirtualInput,
|
||||
capslock_down: bool,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl VirtualInputState {
|
||||
fn new() -> Option<Self> {
|
||||
VirtualInput::new(CGEventSourceStateID::Private, CGEventTapLocation::Session)
|
||||
.map(|virtual_input| Self {
|
||||
virtual_input,
|
||||
capslock_down: false,
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn simulate(&self, event_type: &EventType) -> ResultType<()> {
|
||||
Ok(self.virtual_input.simulate(&event_type)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
static mut VIRTUAL_INPUT_MTX: Mutex<()> = Mutex::new(());
|
||||
#[cfg(target_os = "macos")]
|
||||
static mut VIRTUAL_INPUT: Option<VirtualInput> = None;
|
||||
static mut VIRTUAL_INPUT_STATE: Option<VirtualInputState> = None;
|
||||
|
||||
// First call set_uinput() will create keyboard and mouse clients.
|
||||
// The clients are ipc connections that must live shorter than tokio runtime.
|
||||
@@ -345,6 +480,12 @@ fn is_pressed(key: &Key, en: &mut Enigo) -> bool {
|
||||
get_modifier_state(key.clone(), en)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn key_sleep() {
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_modifier_state(key: Key, en: &mut Enigo) -> bool {
|
||||
// https://github.com/rustdesk/rustdesk/issues/332
|
||||
@@ -413,6 +554,7 @@ pub fn fix_key_down_timeout_at_exit() {
|
||||
log::info!("fix_key_down_timeout_at_exit");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn clear_remapped_keycode() {
|
||||
ENIGO.lock().unwrap().tfc_clear_remapped();
|
||||
@@ -440,6 +582,24 @@ fn record_key_to_key(record_key: u64) -> Option<Key> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release_device_modifiers() {
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
for modifier in [
|
||||
Key::Shift,
|
||||
Key::Control,
|
||||
Key::Alt,
|
||||
Key::Meta,
|
||||
Key::RightShift,
|
||||
Key::RightControl,
|
||||
Key::RightAlt,
|
||||
Key::RWin,
|
||||
] {
|
||||
if get_modifier_state(modifier, &mut en) {
|
||||
en.key_up(modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn release_record_key(record_key: KeysDown) {
|
||||
let func = move || match record_key {
|
||||
@@ -746,7 +906,7 @@ pub fn handle_key(evt: &KeyEvent) {
|
||||
// having GUI, run main GUI thread, otherwise crash
|
||||
let evt = evt.clone();
|
||||
QUEUE.exec_async(move || handle_key_(&evt));
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
key_sleep();
|
||||
return;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
@@ -754,7 +914,7 @@ pub fn handle_key(evt: &KeyEvent) {
|
||||
#[cfg(not(windows))]
|
||||
handle_key_(evt);
|
||||
#[cfg(target_os = "macos")]
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
key_sleep();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -762,8 +922,7 @@ pub fn handle_key(evt: &KeyEvent) {
|
||||
fn reset_input() {
|
||||
unsafe {
|
||||
let _lock = VIRTUAL_INPUT_MTX.lock();
|
||||
VIRTUAL_INPUT =
|
||||
VirtualInput::new(CGEventSourceStateID::Private, CGEventTapLocation::Session).ok();
|
||||
VIRTUAL_INPUT_STATE = VirtualInputState::new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,7 +935,7 @@ pub fn reset_input_ondisconn() {
|
||||
}
|
||||
}
|
||||
|
||||
fn sim_rdev_rawkey_position(code: u32, keydown: bool) {
|
||||
fn sim_rdev_rawkey_position(code: KeyCode, keydown: bool) {
|
||||
#[cfg(target_os = "windows")]
|
||||
let rawkey = RawKey::ScanCode(code);
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -810,13 +969,43 @@ fn sim_rdev_rawkey_virtual(code: u32, keydown: bool) {
|
||||
simulate_(&event_type);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[inline]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn simulate_(event_type: &EventType) {
|
||||
unsafe {
|
||||
let _lock = VIRTUAL_INPUT_MTX.lock();
|
||||
if let Some(virtual_input) = &VIRTUAL_INPUT {
|
||||
let _ = virtual_input.simulate(&event_type);
|
||||
if let Some(input) = &VIRTUAL_INPUT_STATE {
|
||||
let _ = input.simulate(&event_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn press_capslock() {
|
||||
let caps_key = RdevKey::RawKey(rdev::RawKey::MacVirtualKeycode(rdev::kVK_CapsLock));
|
||||
unsafe {
|
||||
let _lock = VIRTUAL_INPUT_MTX.lock();
|
||||
if let Some(input) = &mut VIRTUAL_INPUT_STATE {
|
||||
if input.simulate(&EventType::KeyPress(caps_key)).is_ok() {
|
||||
input.capslock_down = true;
|
||||
key_sleep();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[inline]
|
||||
fn release_capslock() {
|
||||
let caps_key = RdevKey::RawKey(rdev::RawKey::MacVirtualKeycode(rdev::kVK_CapsLock));
|
||||
unsafe {
|
||||
let _lock = VIRTUAL_INPUT_MTX.lock();
|
||||
if let Some(input) = &mut VIRTUAL_INPUT_STATE {
|
||||
if input.simulate(&EventType::KeyRelease(caps_key)).is_ok() {
|
||||
input.capslock_down = false;
|
||||
key_sleep();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -832,14 +1021,6 @@ fn simulate_(event_type: &EventType) {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_modifier_in_key_event(control_key: ControlKey, key_event: &KeyEvent) -> bool {
|
||||
key_event
|
||||
.modifiers
|
||||
.iter()
|
||||
.position(|&m| m == control_key.into())
|
||||
.is_some()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn control_key_value_to_key(value: i32) -> Option<Key> {
|
||||
KEY_MAP.get(&value).and_then(|k| Some(*k))
|
||||
@@ -850,87 +1031,6 @@ fn char_value_to_key(value: u32) -> Key {
|
||||
Key::Layout(std::char::from_u32(value).unwrap_or('\0'))
|
||||
}
|
||||
|
||||
fn is_not_same_status(client_locking: bool, remote_locking: bool) -> bool {
|
||||
client_locking != remote_locking
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn has_numpad_key(key_event: &KeyEvent) -> bool {
|
||||
key_event
|
||||
.modifiers
|
||||
.iter()
|
||||
.filter(|&&ck| NUMPAD_KEY_MAP.get(&ck.value()).is_some())
|
||||
.count()
|
||||
!= 0
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn is_rdev_numpad_key(key_event: &KeyEvent) -> bool {
|
||||
let code = key_event.chr();
|
||||
let key = rdev::get_win_key(code, 0);
|
||||
match key {
|
||||
RdevKey::Home
|
||||
| RdevKey::UpArrow
|
||||
| RdevKey::PageUp
|
||||
| RdevKey::LeftArrow
|
||||
| RdevKey::RightArrow
|
||||
| RdevKey::End
|
||||
| RdevKey::DownArrow
|
||||
| RdevKey::PageDown
|
||||
| RdevKey::Insert
|
||||
| RdevKey::Delete => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn is_numlock_disabled(key_event: &KeyEvent) -> bool {
|
||||
// disable numlock if press home etc when numlock is on,
|
||||
// because we will get numpad value (7,8,9 etc) if not
|
||||
match key_event.mode.unwrap() {
|
||||
KeyboardMode::Map => is_rdev_numpad_key(key_event),
|
||||
_ => has_numpad_key(key_event),
|
||||
}
|
||||
}
|
||||
|
||||
fn click_capslock(en: &mut Enigo) {
|
||||
#[cfg(not(targe_os = "macos"))]
|
||||
en.key_click(enigo::Key::CapsLock);
|
||||
#[cfg(target_os = "macos")]
|
||||
let _ = en.key_down(enigo::Key::CapsLock);
|
||||
}
|
||||
|
||||
fn click_numlock(_en: &mut Enigo) {
|
||||
// without numlock in macos
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
_en.key_click(enigo::Key::NumLock);
|
||||
}
|
||||
|
||||
fn sync_numlock_capslock_status(key_event: &KeyEvent) {
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
|
||||
let client_caps_locking = is_modifier_in_key_event(ControlKey::CapsLock, key_event);
|
||||
let client_num_locking = is_modifier_in_key_event(ControlKey::NumLock, key_event);
|
||||
let remote_caps_locking = en.get_key_state(enigo::Key::CapsLock);
|
||||
let remote_num_locking = en.get_key_state(enigo::Key::NumLock);
|
||||
|
||||
let need_click_capslock = is_not_same_status(client_caps_locking, remote_caps_locking);
|
||||
let need_click_numlock = is_not_same_status(client_num_locking, remote_num_locking);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let disable_numlock = false;
|
||||
#[cfg(target_os = "windows")]
|
||||
let disable_numlock = is_numlock_disabled(key_event);
|
||||
|
||||
if need_click_capslock {
|
||||
click_capslock(&mut en);
|
||||
}
|
||||
|
||||
if need_click_numlock && !disable_numlock {
|
||||
click_numlock(&mut en);
|
||||
}
|
||||
}
|
||||
|
||||
fn map_keyboard_mode(evt: &KeyEvent) {
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::try_change_desktop();
|
||||
@@ -949,7 +1049,7 @@ fn map_keyboard_mode(evt: &KeyEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
sim_rdev_rawkey_position(evt.chr(), evt.down);
|
||||
sim_rdev_rawkey_position(evt.chr() as _, evt.down);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -1085,7 +1185,9 @@ fn is_function_key(ck: &EnumOrUnknown<ControlKey>) -> bool {
|
||||
});
|
||||
res = true;
|
||||
} else if ck.value() == ControlKey::LockScreen.value() {
|
||||
lock_screen_2();
|
||||
std::thread::spawn(|| {
|
||||
lock_screen_2();
|
||||
});
|
||||
res = true;
|
||||
}
|
||||
return res;
|
||||
@@ -1127,7 +1229,7 @@ fn legacy_keyboard_mode(evt: &KeyEvent) {
|
||||
fn translate_process_code(code: u32, down: bool) {
|
||||
crate::platform::windows::try_change_desktop();
|
||||
match code >> 16 {
|
||||
0 => sim_rdev_rawkey_position(code, down),
|
||||
0 => sim_rdev_rawkey_position(code as _, down),
|
||||
vk_code => sim_rdev_rawkey_virtual(vk_code, down),
|
||||
};
|
||||
}
|
||||
@@ -1143,40 +1245,169 @@ fn translate_keyboard_mode(evt: &KeyEvent) {
|
||||
// remote: Shift + 1 => 1
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(target_os = "macos")]
|
||||
en.key_sequence(seq);
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
{
|
||||
simulate_(&EventType::KeyRelease(RdevKey::ShiftLeft));
|
||||
simulate_(&EventType::KeyRelease(RdevKey::ShiftRight));
|
||||
if get_modifier_state(Key::Shift, &mut en) {
|
||||
simulate_(&EventType::KeyRelease(RdevKey::ShiftLeft));
|
||||
}
|
||||
if get_modifier_state(Key::RightShift, &mut en) {
|
||||
simulate_(&EventType::KeyRelease(RdevKey::ShiftRight));
|
||||
}
|
||||
for chr in seq.chars() {
|
||||
#[cfg(target_os = "windows")]
|
||||
rdev::simulate_char(chr, true).ok();
|
||||
#[cfg(target_os = "linux")]
|
||||
en.key_click(Key::Layout(chr));
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
en.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);
|
||||
sim_rdev_rawkey_position(evt.chr() as _, evt.down);
|
||||
}
|
||||
Some(key_event::Union::Unicode(..)) => {
|
||||
// Do not handle unicode for now.
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
Some(key_event::Union::Win2winHotkey(code)) => {
|
||||
simulate_win2win_hotkey(*code, evt.down);
|
||||
}
|
||||
_ => {
|
||||
log::debug!("Unreachable. Unexpected key event {:?}", &evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn simulate_win2win_hotkey(code: u32, down: bool) {
|
||||
let unicode: u16 = (code & 0x0000FFFF) as u16;
|
||||
if down {
|
||||
if rdev::simulate_key_unicode(unicode, false).is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let keycode: u16 = ((code >> 16) & 0x0000FFFF) as u16;
|
||||
let scan = rdev::vk_to_scancode(keycode as _);
|
||||
allow_err!(rdev::simulate_code(None, Some(scan), down));
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
||||
fn skip_led_sync_control_key(_key: &ControlKey) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// LockModesHandler should not be created when single meta is pressing and releasing.
|
||||
// Because the drop function may insert "CapsLock Click" and "NumLock Click", which breaks single meta click.
|
||||
// https://github.com/rustdesk/rustdesk/issues/3928#issuecomment-1496936687
|
||||
// https://github.com/rustdesk/rustdesk/issues/3928#issuecomment-1500415822
|
||||
// https://github.com/rustdesk/rustdesk/issues/3928#issuecomment-1500773473
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
fn skip_led_sync_control_key(key: &ControlKey) -> bool {
|
||||
matches!(
|
||||
key,
|
||||
ControlKey::Control
|
||||
| ControlKey::RControl
|
||||
| ControlKey::Meta
|
||||
| ControlKey::Shift
|
||||
| ControlKey::RShift
|
||||
| ControlKey::Alt
|
||||
| ControlKey::RAlt
|
||||
| ControlKey::Tab
|
||||
| ControlKey::Return
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
fn is_numpad_control_key(key: &ControlKey) -> bool {
|
||||
matches!(
|
||||
key,
|
||||
ControlKey::Numpad0
|
||||
| ControlKey::Numpad1
|
||||
| ControlKey::Numpad2
|
||||
| ControlKey::Numpad3
|
||||
| ControlKey::Numpad4
|
||||
| ControlKey::Numpad5
|
||||
| ControlKey::Numpad6
|
||||
| ControlKey::Numpad7
|
||||
| ControlKey::Numpad8
|
||||
| ControlKey::Numpad9
|
||||
| ControlKey::NumpadEnter
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
||||
fn skip_led_sync_rdev_key(_key: &RdevKey) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
fn skip_led_sync_rdev_key(key: &RdevKey) -> bool {
|
||||
matches!(
|
||||
key,
|
||||
RdevKey::ControlLeft
|
||||
| RdevKey::ControlRight
|
||||
| RdevKey::MetaLeft
|
||||
| RdevKey::MetaRight
|
||||
| RdevKey::ShiftLeft
|
||||
| RdevKey::ShiftRight
|
||||
| RdevKey::Alt
|
||||
| RdevKey::AltGr
|
||||
| RdevKey::Tab
|
||||
| RdevKey::Return
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn is_legacy_mode(evt: &KeyEvent) -> bool {
|
||||
evt.mode.enum_value_or(KeyboardMode::Legacy) == KeyboardMode::Legacy
|
||||
}
|
||||
|
||||
pub fn handle_key_(evt: &KeyEvent) {
|
||||
if EXITING.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
|
||||
if evt.down {
|
||||
sync_numlock_capslock_status(evt)
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let mut _lock_mode_handler = None;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
match &evt.union {
|
||||
Some(key_event::Union::Unicode(..)) | Some(key_event::Union::Seq(..)) => {
|
||||
_lock_mode_handler = Some(LockModesHandler::new_handler(&evt, false));
|
||||
}
|
||||
Some(key_event::Union::ControlKey(ck)) => {
|
||||
let key = ck.enum_value_or(ControlKey::Unknown);
|
||||
if !skip_led_sync_control_key(&key) {
|
||||
#[cfg(target_os = "macos")]
|
||||
let is_numpad_key = false;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
let is_numpad_key = is_numpad_control_key(&key);
|
||||
_lock_mode_handler = Some(LockModesHandler::new_handler(&evt, is_numpad_key));
|
||||
}
|
||||
}
|
||||
Some(key_event::Union::Chr(code)) => {
|
||||
if is_legacy_mode(&evt) {
|
||||
_lock_mode_handler = Some(LockModesHandler::new_handler(evt, false));
|
||||
} else {
|
||||
let key = crate::keyboard::keycode_to_rdev_key(*code);
|
||||
if !skip_led_sync_rdev_key(&key) {
|
||||
#[cfg(target_os = "macos")]
|
||||
let is_numpad_key = false;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
let is_numpad_key = crate::keyboard::is_numpad_rdev_key(&key);
|
||||
_lock_mode_handler = Some(LockModesHandler::new_handler(evt, is_numpad_key));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
match evt.mode.unwrap() {
|
||||
KeyboardMode::Map => {
|
||||
map_keyboard_mode(evt);
|
||||
|
||||
@@ -479,12 +479,11 @@ pub mod client {
|
||||
)?);
|
||||
shutdown_hooks::add_shutdown_hook(drop_portable_service_shared_memory);
|
||||
}
|
||||
let mut option = SHMEM.lock().unwrap();
|
||||
let shmem = option.as_mut().unwrap();
|
||||
unsafe {
|
||||
libc::memset(shmem.as_ptr() as _, 0, shmem.len() as _);
|
||||
if let Some(shmem) = SHMEM.lock().unwrap().as_mut() {
|
||||
unsafe {
|
||||
libc::memset(shmem.as_ptr() as _, 0, shmem.len() as _);
|
||||
}
|
||||
}
|
||||
drop(option);
|
||||
match para {
|
||||
StartPara::Direct => {
|
||||
if let Err(e) = crate::platform::run_background(
|
||||
@@ -544,8 +543,11 @@ pub mod client {
|
||||
}
|
||||
|
||||
pub extern "C" fn drop_portable_service_shared_memory() {
|
||||
log::info!("drop shared memory");
|
||||
*SHMEM.lock().unwrap() = None;
|
||||
let mut lock = SHMEM.lock().unwrap();
|
||||
if lock.is_some() {
|
||||
*lock = None;
|
||||
log::info!("drop shared memory");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_quick_support(v: bool) {
|
||||
@@ -560,17 +562,18 @@ pub mod client {
|
||||
Self: Sized,
|
||||
{
|
||||
let mut option = SHMEM.lock().unwrap();
|
||||
let shmem = option.as_mut().unwrap();
|
||||
Self::set_para(
|
||||
shmem,
|
||||
CapturerPara {
|
||||
current_display,
|
||||
use_yuv,
|
||||
use_yuv_set: false,
|
||||
timeout_ms: 33,
|
||||
},
|
||||
);
|
||||
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE));
|
||||
if let Some(shmem) = option.as_mut() {
|
||||
Self::set_para(
|
||||
shmem,
|
||||
CapturerPara {
|
||||
current_display,
|
||||
use_yuv,
|
||||
use_yuv_set: false,
|
||||
timeout_ms: 33,
|
||||
},
|
||||
);
|
||||
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE));
|
||||
}
|
||||
CapturerPortable {}
|
||||
}
|
||||
|
||||
@@ -587,25 +590,29 @@ pub mod client {
|
||||
impl TraitCapturer for CapturerPortable {
|
||||
fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
let mut option = SHMEM.lock().unwrap();
|
||||
let shmem = option.as_mut().unwrap();
|
||||
unsafe {
|
||||
let para_ptr = shmem.as_ptr().add(ADDR_CAPTURER_PARA);
|
||||
let para = para_ptr as *const CapturerPara;
|
||||
Self::set_para(
|
||||
shmem,
|
||||
CapturerPara {
|
||||
current_display: (*para).current_display,
|
||||
use_yuv,
|
||||
use_yuv_set: true,
|
||||
timeout_ms: (*para).timeout_ms,
|
||||
},
|
||||
);
|
||||
if let Some(shmem) = option.as_mut() {
|
||||
unsafe {
|
||||
let para_ptr = shmem.as_ptr().add(ADDR_CAPTURER_PARA);
|
||||
let para = para_ptr as *const CapturerPara;
|
||||
Self::set_para(
|
||||
shmem,
|
||||
CapturerPara {
|
||||
current_display: (*para).current_display,
|
||||
use_yuv,
|
||||
use_yuv_set: true,
|
||||
timeout_ms: (*para).timeout_ms,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn frame<'a>(&'a mut self, timeout: Duration) -> std::io::Result<Frame<'a>> {
|
||||
let mut option = SHMEM.lock().unwrap();
|
||||
let shmem = option.as_mut().unwrap();
|
||||
let mut lock = SHMEM.lock().unwrap();
|
||||
let shmem = lock.as_mut().ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"shmem dropped".to_string(),
|
||||
))?;
|
||||
unsafe {
|
||||
let base = shmem.as_ptr();
|
||||
let para_ptr = base.add(ADDR_CAPTURER_PARA);
|
||||
@@ -801,7 +808,10 @@ pub mod client {
|
||||
|
||||
pub fn get_cursor_info(pci: PCURSORINFO) -> BOOL {
|
||||
if RUNNING.lock().unwrap().clone() {
|
||||
get_cursor_info_(&mut SHMEM.lock().unwrap().as_mut().unwrap(), pci)
|
||||
let mut option = SHMEM.lock().unwrap();
|
||||
option
|
||||
.as_mut()
|
||||
.map_or(FALSE, |sheme| get_cursor_info_(sheme, pci))
|
||||
} else {
|
||||
unsafe { winuser::GetCursorInfo(pci) }
|
||||
}
|
||||
|
||||
@@ -221,6 +221,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
||||
thread::sleep(interval - elapsed);
|
||||
}
|
||||
}
|
||||
log::info!("Service {} exit", sp.name());
|
||||
});
|
||||
self.0.write().unwrap().handle = Some(thread);
|
||||
}
|
||||
@@ -256,6 +257,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
||||
}
|
||||
thread::sleep(time::Duration::from_millis(HIBERNATE_TIMEOUT));
|
||||
}
|
||||
log::info!("Service {} exit", sp.name());
|
||||
});
|
||||
self.0.write().unwrap().handle = Some(thread);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
pub const FPS: u8 = 30;
|
||||
pub const MIN_FPS: u8 = 10;
|
||||
pub const MIN_FPS: u8 = 1;
|
||||
pub const MAX_FPS: u8 = 120;
|
||||
trait Percent {
|
||||
fn as_percent(&self) -> u32;
|
||||
@@ -221,7 +221,9 @@ impl VideoQoS {
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
*self = Default::default();
|
||||
self.fps = FPS;
|
||||
self.user_fps = FPS;
|
||||
self.updated = true;
|
||||
}
|
||||
|
||||
pub fn check_abr_config(&mut self) -> bool {
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
// https://slhck.info/video/2017/03/01/rate-control.html
|
||||
|
||||
use super::{video_qos::VideoQoS, *};
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
use crate::virtual_display_manager;
|
||||
#[cfg(windows)]
|
||||
use crate::{platform::windows::is_process_consent_running, privacy_win_mag};
|
||||
#[cfg(windows)]
|
||||
use hbb_common::get_version_number;
|
||||
use hbb_common::tokio::sync::{
|
||||
@@ -31,7 +35,7 @@ use scrap::{
|
||||
codec::{Encoder, EncoderCfg, HwEncoderConfig},
|
||||
record::{Recorder, RecorderContext},
|
||||
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
|
||||
Display, TraitCapturer,
|
||||
CodecName, Display, TraitCapturer,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use std::sync::Once;
|
||||
@@ -41,14 +45,6 @@ use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
time::{self, Duration, Instant},
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use virtual_display;
|
||||
|
||||
pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version.";
|
||||
pub const SCRAP_OTHER_VERSION_OR_X11_REQUIRED: &str =
|
||||
"Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.";
|
||||
pub const SCRAP_X11_REQUIRED: &str = "x11 expected";
|
||||
pub const SCRAP_X11_REF_URL: &str = "https://rustdesk.com/docs/en/manual/linux/#x11-required";
|
||||
|
||||
pub const NAME: &'static str = "video";
|
||||
|
||||
@@ -208,8 +204,6 @@ fn create_capturer(
|
||||
if privacy_mode_id > 0 {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use crate::win_privacy::*;
|
||||
|
||||
match scrap::CapturerMag::new(
|
||||
display.origin(),
|
||||
display.width(),
|
||||
@@ -220,7 +214,7 @@ fn create_capturer(
|
||||
let mut ok = false;
|
||||
let check_begin = Instant::now();
|
||||
while check_begin.elapsed().as_secs() < 5 {
|
||||
match c1.exclude("", PRIVACY_WINDOW_NAME) {
|
||||
match c1.exclude("", privacy_win_mag::PRIVACY_WINDOW_NAME) {
|
||||
Ok(false) => {
|
||||
ok = false;
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
@@ -229,7 +223,7 @@ fn create_capturer(
|
||||
bail!(
|
||||
"Failed to exclude privacy window {} - {}, err: {}",
|
||||
"",
|
||||
PRIVACY_WINDOW_NAME,
|
||||
privacy_win_mag::PRIVACY_WINDOW_NAME,
|
||||
e
|
||||
);
|
||||
}
|
||||
@@ -243,7 +237,7 @@ fn create_capturer(
|
||||
bail!(
|
||||
"Failed to exclude privacy window {} - {} ",
|
||||
"",
|
||||
PRIVACY_WINDOW_NAME
|
||||
privacy_win_mag::PRIVACY_WINDOW_NAME
|
||||
);
|
||||
}
|
||||
log::debug!("Create magnifier capture for {}", privacy_mode_id);
|
||||
@@ -275,18 +269,12 @@ fn create_capturer(
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
// to-do: do not close if in privacy mode.
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
fn ensure_close_virtual_device() -> ResultType<()> {
|
||||
let num_displays = Display::all()?.len();
|
||||
if num_displays == 0 {
|
||||
// Device may sometimes be uninstalled by user in "Device Manager" Window.
|
||||
// Closing device will clear the instance data.
|
||||
virtual_display::close_device();
|
||||
} else if num_displays > 1 {
|
||||
// Try close device, if display device changed.
|
||||
if virtual_display::is_device_created() {
|
||||
virtual_display::close_device();
|
||||
}
|
||||
if num_displays > 1 {
|
||||
virtual_display_manager::plug_out_headless();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -309,11 +297,11 @@ pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
|
||||
fn check_uac_switch(privacy_mode_id: i32, capturer_privacy_mode_id: i32) -> ResultType<()> {
|
||||
if capturer_privacy_mode_id != 0 {
|
||||
if privacy_mode_id != capturer_privacy_mode_id {
|
||||
if !crate::win_privacy::is_process_consent_running()? {
|
||||
if !is_process_consent_running()? {
|
||||
bail!("consent.exe is running");
|
||||
}
|
||||
}
|
||||
if crate::win_privacy::is_process_consent_running()? {
|
||||
if is_process_consent_running()? {
|
||||
bail!("consent.exe is running");
|
||||
}
|
||||
}
|
||||
@@ -374,7 +362,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
|
||||
let mut capturer_privacy_mode_id = privacy_mode_id;
|
||||
#[cfg(windows)]
|
||||
if capturer_privacy_mode_id != 0 {
|
||||
if crate::win_privacy::is_process_consent_running()? {
|
||||
if is_process_consent_running()? {
|
||||
capturer_privacy_mode_id = 0;
|
||||
}
|
||||
}
|
||||
@@ -431,7 +419,7 @@ fn check_displays_new() -> Option<Vec<Display>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_displays_changed() -> Option<Message> {
|
||||
fn check_get_displays_changed_msg() -> Option<Message> {
|
||||
let displays = check_displays_new()?;
|
||||
let (current, displays) = get_displays_2(&displays);
|
||||
let mut pi = PeerInfo {
|
||||
@@ -447,7 +435,7 @@ fn check_displays_changed() -> Option<Message> {
|
||||
}
|
||||
|
||||
fn run(sp: GenericService) -> ResultType<()> {
|
||||
#[cfg(windows)]
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
ensure_close_virtual_device()?;
|
||||
|
||||
// ensure_inited() is needed because release_resource() may be called.
|
||||
@@ -468,21 +456,29 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
drop(video_qos);
|
||||
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
|
||||
|
||||
let encoder_cfg = match Encoder::current_hw_encoder_name() {
|
||||
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
|
||||
codec_name,
|
||||
width: c.width,
|
||||
height: c.height,
|
||||
bitrate: bitrate as _,
|
||||
}),
|
||||
None => EncoderCfg::VPX(VpxEncoderConfig {
|
||||
width: c.width as _,
|
||||
height: c.height as _,
|
||||
timebase: [1, 1000], // Output timestamp precision
|
||||
bitrate,
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
}),
|
||||
let encoder_cfg = match Encoder::negotiated_codec() {
|
||||
scrap::CodecName::H264(name) | scrap::CodecName::H265(name) => {
|
||||
EncoderCfg::HW(HwEncoderConfig {
|
||||
name,
|
||||
width: c.width,
|
||||
height: c.height,
|
||||
bitrate: bitrate as _,
|
||||
})
|
||||
}
|
||||
name @ (scrap::CodecName::VP8 | scrap::CodecName::VP9) => {
|
||||
EncoderCfg::VPX(VpxEncoderConfig {
|
||||
width: c.width as _,
|
||||
height: c.height as _,
|
||||
timebase: [1, 1000], // Output timestamp precision
|
||||
bitrate,
|
||||
codec: if name == scrap::CodecName::VP8 {
|
||||
VpxVideoCodecId::VP8
|
||||
} else {
|
||||
VpxVideoCodecId::VP9
|
||||
},
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let mut encoder;
|
||||
@@ -526,7 +522,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
let mut try_gdi = 1;
|
||||
#[cfg(windows)]
|
||||
log::info!("gdi: {}", c.is_gdi());
|
||||
let codec_name = Encoder::current_hw_encoder_name();
|
||||
let codec_name = Encoder::negotiated_codec();
|
||||
let recorder = get_recorder(c.width, c.height, &codec_name);
|
||||
#[cfg(windows)]
|
||||
start_uac_elevation_check();
|
||||
@@ -539,7 +535,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
check_uac_switch(c.privacy_mode_id, c._capturer_privacy_mode_id)?;
|
||||
|
||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||
if video_qos.check_if_updated() {
|
||||
if video_qos.check_if_updated() && video_qos.target_bitrate > 0 {
|
||||
log::debug!(
|
||||
"qos is updated, target_bitrate:{}, fps:{}",
|
||||
video_qos.target_bitrate,
|
||||
@@ -557,7 +553,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
*SWITCH.lock().unwrap() = true;
|
||||
bail!("SWITCH");
|
||||
}
|
||||
if codec_name != Encoder::current_hw_encoder_name() {
|
||||
if codec_name != Encoder::negotiated_codec() {
|
||||
bail!("SWITCH");
|
||||
}
|
||||
#[cfg(windows)]
|
||||
@@ -585,11 +581,8 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
bail!("SWITCH");
|
||||
}
|
||||
|
||||
if let Some(msg_out) = check_displays_changed() {
|
||||
if let Some(msg_out) = check_get_displays_changed_msg() {
|
||||
sp.send(msg_out);
|
||||
}
|
||||
|
||||
if c.ndisplay != get_display_num() {
|
||||
log::info!("Displays changed");
|
||||
*SWITCH.lock().unwrap() = true;
|
||||
bail!("SWITCH");
|
||||
@@ -606,8 +599,14 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
let time = now - start;
|
||||
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
||||
match frame {
|
||||
scrap::Frame::VP8(data) => {
|
||||
let send_conn_ids =
|
||||
handle_one_frame_encoded(VpxVideoCodecId::VP8, &sp, data, ms)?;
|
||||
frame_controller.set_send(now, send_conn_ids);
|
||||
}
|
||||
scrap::Frame::VP9(data) => {
|
||||
let send_conn_ids = handle_one_frame_encoded(&sp, data, ms)?;
|
||||
let send_conn_ids =
|
||||
handle_one_frame_encoded(VpxVideoCodecId::VP9, &sp, data, ms)?;
|
||||
frame_controller.set_send(now, send_conn_ids);
|
||||
}
|
||||
scrap::Frame::RAW(data) => {
|
||||
@@ -720,12 +719,11 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
fn get_recorder(
|
||||
width: usize,
|
||||
height: usize,
|
||||
codec_name: &Option<String>,
|
||||
codec_name: &CodecName,
|
||||
) -> Arc<Mutex<Option<Recorder>>> {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
let recorder = if !Config::get_option("allow-auto-record-incoming").is_empty() {
|
||||
use crate::hbbs_http::record_upload;
|
||||
use scrap::record::RecordCodecID::*;
|
||||
|
||||
let tx = if record_upload::is_enable() {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
@@ -734,16 +732,6 @@ fn get_recorder(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let codec_id = match codec_name {
|
||||
Some(name) => {
|
||||
if name.contains("264") {
|
||||
H264
|
||||
} else {
|
||||
H265
|
||||
}
|
||||
}
|
||||
None => VP9,
|
||||
};
|
||||
Recorder::new(RecorderContext {
|
||||
server: true,
|
||||
id: Config::get_id(),
|
||||
@@ -751,7 +739,7 @@ fn get_recorder(
|
||||
filename: "".to_owned(),
|
||||
width,
|
||||
height,
|
||||
codec_id,
|
||||
format: codec_name.into(),
|
||||
tx,
|
||||
})
|
||||
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))))
|
||||
@@ -778,20 +766,6 @@ fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> Resu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
fn create_msg(vp9s: Vec<EncodedVideoFrame>) -> Message {
|
||||
let mut msg_out = Message::new();
|
||||
let mut vf = VideoFrame::new();
|
||||
vf.set_vp9s(EncodedVideoFrames {
|
||||
frames: vp9s.into(),
|
||||
..Default::default()
|
||||
});
|
||||
vf.timestamp = hbb_common::get_time();
|
||||
msg_out.set_video_frame(vf);
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handle_one_frame(
|
||||
sp: &GenericService,
|
||||
@@ -824,6 +798,7 @@ fn handle_one_frame(
|
||||
#[inline]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub fn handle_one_frame_encoded(
|
||||
codec: VpxVideoCodecId,
|
||||
sp: &GenericService,
|
||||
frame: &[u8],
|
||||
ms: i64,
|
||||
@@ -835,32 +810,16 @@ pub fn handle_one_frame_encoded(
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
let mut send_conn_ids: HashSet<i32> = Default::default();
|
||||
let vp9_frame = EncodedVideoFrame {
|
||||
let vpx_frame = EncodedVideoFrame {
|
||||
data: frame.to_vec().into(),
|
||||
key: true,
|
||||
pts: ms,
|
||||
..Default::default()
|
||||
};
|
||||
send_conn_ids = sp.send_video_frame(create_msg(vec![vp9_frame]));
|
||||
let send_conn_ids = sp.send_video_frame(scrap::VpxEncoder::create_msg(codec, vec![vpx_frame]));
|
||||
Ok(send_conn_ids)
|
||||
}
|
||||
|
||||
fn get_display_num() -> usize {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !scrap::is_x11() {
|
||||
return if let Ok(n) = super::wayland::get_display_num() {
|
||||
n
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
LAST_SYNC_DISPLAYS.read().unwrap().len()
|
||||
}
|
||||
|
||||
pub(super) fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
|
||||
let mut displays = Vec::new();
|
||||
let mut primary = 0;
|
||||
@@ -951,37 +910,24 @@ pub async fn switch_to_primary() {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
|
||||
fn try_get_displays() -> ResultType<Vec<Display>> {
|
||||
Ok(Display::all()?)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
fn try_get_displays() -> ResultType<Vec<Display>> {
|
||||
let mut displays = Display::all()?;
|
||||
if displays.len() == 0 {
|
||||
log::debug!("no displays, create virtual display");
|
||||
// Try plugin monitor
|
||||
if !virtual_display::is_device_created() {
|
||||
if let Err(e) = virtual_display::create_device() {
|
||||
log::debug!("Create device failed {}", e);
|
||||
}
|
||||
if let Err(e) = virtual_display_manager::plug_in_headless() {
|
||||
log::error!("plug in headless failed {}", e);
|
||||
} else {
|
||||
displays = Display::all()?;
|
||||
}
|
||||
if virtual_display::is_device_created() {
|
||||
if let Err(e) = virtual_display::plug_in_monitor() {
|
||||
log::debug!("Plug in monitor failed {}", e);
|
||||
} else {
|
||||
if let Err(e) = virtual_display::update_monitor_modes() {
|
||||
log::debug!("Update monitor modes failed {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
displays = Display::all()?;
|
||||
} else if displays.len() > 1 {
|
||||
// If more than one displays exists, close RustDeskVirtualDisplay
|
||||
if virtual_display::is_device_created() {
|
||||
virtual_display::close_device()
|
||||
}
|
||||
let _res = virtual_display_manager::plug_in_headless();
|
||||
}
|
||||
Ok(displays)
|
||||
}
|
||||
@@ -1020,7 +966,7 @@ fn start_uac_elevation_check() {
|
||||
if !crate::platform::is_installed() && !crate::platform::is_root() {
|
||||
std::thread::spawn(|| loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Ok(uac) = crate::win_privacy::is_process_consent_running() {
|
||||
if let Ok(uac) = is_process_consent_running() {
|
||||
*IS_UAC_RUNNING.lock().unwrap() = uac;
|
||||
}
|
||||
if !crate::platform::is_elevated(None).unwrap_or(false) {
|
||||
|
||||
@@ -3,7 +3,7 @@ use hbb_common::{allow_err, platform::linux::DISTRO};
|
||||
use scrap::{is_cursor_embedded, set_map_err, Capturer, Display, Frame, TraitCapturer};
|
||||
use std::io;
|
||||
|
||||
use super::video_service::{
|
||||
use crate::client::{
|
||||
SCRAP_OTHER_VERSION_OR_X11_REQUIRED, SCRAP_UBUNTU_HIGHER_REQUIRED, SCRAP_X11_REQUIRED,
|
||||
};
|
||||
|
||||
@@ -230,19 +230,6 @@ pub(super) fn get_primary() -> ResultType<usize> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_display_num() -> ResultType<usize> {
|
||||
let addr = *CAP_DISPLAY_INFO.read().unwrap();
|
||||
if addr != 0 {
|
||||
let cap_display_info: *const CapDisplayInfo = addr as _;
|
||||
unsafe {
|
||||
let cap_display_info = &*cap_display_info;
|
||||
Ok(cap_display_info.num)
|
||||
}
|
||||
} else {
|
||||
bail!("Failed to get capturer display info");
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn release_resource() {
|
||||
if scrap::is_x11() {
|
||||
|
||||
17
src/ui.rs
17
src/ui.rs
@@ -54,6 +54,18 @@ pub fn start(args: &mut [String]) {
|
||||
let dir = "/usr";
|
||||
sciter::set_library(&(prefix + dir + "/lib/rustdesk/libsciter-gtk.so")).ok();
|
||||
}
|
||||
#[cfg(windows)]
|
||||
// Check if there is a sciter.dll nearby.
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(parent) = exe.parent() {
|
||||
let sciter_dll_path = parent.join("sciter.dll");
|
||||
if sciter_dll_path.exists() {
|
||||
// Try to set the sciter dll.
|
||||
let p = sciter_dll_path.to_string_lossy().to_string();
|
||||
log::debug!("Found dll:{}, \n {:?}", p, sciter::set_library(&p));
|
||||
}
|
||||
}
|
||||
}
|
||||
// https://github.com/c-smile/sciter-sdk/blob/master/include/sciter-x-types.h
|
||||
// https://github.com/rustdesk/rustdesk/issues/132#issuecomment-886069737
|
||||
#[cfg(windows)]
|
||||
@@ -458,6 +470,10 @@ impl UI {
|
||||
get_version()
|
||||
}
|
||||
|
||||
fn get_fingerprint(&self) -> String {
|
||||
get_fingerprint()
|
||||
}
|
||||
|
||||
fn get_app_name(&self) -> String {
|
||||
get_app_name()
|
||||
}
|
||||
@@ -637,6 +653,7 @@ impl sciter::EventHandler for UI {
|
||||
fn get_software_update_url();
|
||||
fn get_new_version();
|
||||
fn get_version();
|
||||
fn get_fingerprint();
|
||||
fn update_me(String);
|
||||
fn show_run_without_install();
|
||||
fn run_without_install();
|
||||
|
||||
@@ -142,6 +142,10 @@ div.password input {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
div.username input {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
svg {
|
||||
background: none;
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ function msgbox(type, title, content, link="", callback=null, height=180, width=
|
||||
view.close();
|
||||
return;
|
||||
}
|
||||
handler.login(res.password, res.remember);
|
||||
handler.login("", "", res.password, res.remember);
|
||||
if (!is_port_forward) {
|
||||
// Specially handling file transfer for no permission hanging issue (including 60ms
|
||||
// timer in setPermission.
|
||||
@@ -262,6 +262,30 @@ function msgbox(type, title, content, link="", callback=null, height=180, width=
|
||||
else msgbox("connecting", "Connecting...", "Logging in...");
|
||||
}
|
||||
};
|
||||
} else if (type == "session-login" || type == "session-re-login") {
|
||||
callback = function (res) {
|
||||
if (!res) {
|
||||
view.close();
|
||||
return;
|
||||
}
|
||||
handler.login(res.osusername, res.ospassword, "", false);
|
||||
if (!is_port_forward) {
|
||||
if (is_file_transfer) handler.msgbox("connecting", "Connecting...", "Logging in...");
|
||||
else msgbox("connecting", "Connecting...", "Logging in...");
|
||||
}
|
||||
};
|
||||
} else if (type.indexOf("session-login") >= 0) {
|
||||
callback = function (res) {
|
||||
if (!res) {
|
||||
view.close();
|
||||
return;
|
||||
}
|
||||
handler.login(res.osusername, res.ospassword, res.password, res.remember);
|
||||
if (!is_port_forward) {
|
||||
if (is_file_transfer) handler.msgbox("connecting", "Connecting...", "Logging in...");
|
||||
else msgbox("connecting", "Connecting...", "Logging in...");
|
||||
}
|
||||
};
|
||||
} else if (type.indexOf("custom") < 0 && !is_port_forward && !callback) {
|
||||
callback = function() { view.close(); }
|
||||
} else if (type == 'wait-remote-accept-nook') {
|
||||
|
||||
@@ -161,8 +161,8 @@ class Header: Reactor.Component {
|
||||
}
|
||||
|
||||
function renderDisplayPop() {
|
||||
var codecs = handler.supported_hwcodec();
|
||||
var show_codec = handler.has_hwcodec() && (codecs[0] || codecs[1]);
|
||||
var codecs = handler.alternative_codecs();
|
||||
var show_codec = codecs[0] || codecs[1] || codecs[2];
|
||||
|
||||
var cursor_embedded = false;
|
||||
if ((pi.displays || []).length > 0) {
|
||||
@@ -186,9 +186,10 @@ class Header: Reactor.Component {
|
||||
{show_codec ? <div>
|
||||
<div .separator />
|
||||
<li #auto type="codec-preference"><span>{svg_checkmark}</span>Auto</li>
|
||||
{codecs[0] ? <li #vp8 type="codec-preference"><span>{svg_checkmark}</span>VP8</li> : ""}
|
||||
<li #vp9 type="codec-preference"><span>{svg_checkmark}</span>VP9</li>
|
||||
{codecs[0] ? <li #h264 type="codec-preference"><span>{svg_checkmark}</span>H264</li> : ""}
|
||||
{codecs[1] ? <li #h265 type="codec-preference"><span>{svg_checkmark}</span>H265</li> : ""}
|
||||
{codecs[1] ? <li #h264 type="codec-preference"><span>{svg_checkmark}</span>H264</li> : ""}
|
||||
{codecs[2] ? <li #h265 type="codec-preference"><span>{svg_checkmark}</span>H265</li> : ""}
|
||||
</div> : ""}
|
||||
<div .separator />
|
||||
{!cursor_embedded && <li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>}
|
||||
|
||||
@@ -311,7 +311,7 @@ class MyIdMenu: Reactor.Component {
|
||||
<li #stop-service class={service_stopped ? "line-through" : "selected"}><span>{svg_checkmark}</span>{translate("Enable Service")}</li>
|
||||
{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>}
|
||||
{false && handler.using_public_server() && <li #allow-always-relay><span>{svg_checkmark}</span>{translate('Always connect via relay')}</li>}
|
||||
{handler.is_ok_change_id() ? <div .separator /> : ""}
|
||||
{username ?
|
||||
<li #logout>{translate('Logout')} ({username})</li> :
|
||||
@@ -364,6 +364,7 @@ class MyIdMenu: Reactor.Component {
|
||||
var name = handler.get_app_name();
|
||||
msgbox("custom-nocancel-nook-hasclose", translate("About") + " " + name, "<div style='line-height: 2em'> \
|
||||
<div>Version: " + handler.get_version() + " \
|
||||
<div>Fingerprint: " + handler.get_fingerprint() + " \
|
||||
<div .link .custom-event url='https://rustdesk.com/privacy'>Privacy Statement</div> \
|
||||
<div .link .custom-event url='https://rustdesk.com'>Website</div> \
|
||||
<div style='background: #2c8cff; color: white; padding: 1em; margin-top: 1em;'>Copyright © 2022 Purslane Ltd.\
|
||||
|
||||
@@ -32,7 +32,7 @@ class MsgboxComponent: Reactor.Component {
|
||||
}
|
||||
|
||||
function getIcon(color) {
|
||||
if (this.type == "input-password") {
|
||||
if (this.type == "input-password" || this.type == "session-login" || this.type == "session-login-password") {
|
||||
return <svg viewBox="0 0 505 505"><circle cx="252.5" cy="252.5" r="252.5" fill={color}/><path d="M271.9 246.1c29.2 17.5 67.6 13.6 92.7-11.5 29.7-29.7 29.7-77.8 0-107.4s-77.8-29.7-107.4 0c-25.1 25.1-29 63.5-11.5 92.7L118.1 347.4l26.2 26.2 26.4 26.4 10.6-10.6-10.1-10.1 9.7-9.7 10.1 10.1 10.6-10.6-10.1-10 9.7-9.7 10.1 10.1 10.6-10.6-26.4-26.3 76.4-76.5z" fill="#fff"/><circle cx="337.4" cy="154.4" r="17.7" fill={color}/></svg>;
|
||||
}
|
||||
if (this.type == "connecting") {
|
||||
@@ -41,7 +41,7 @@ class MsgboxComponent: Reactor.Component {
|
||||
if (this.type == "success") {
|
||||
return <svg viewBox="0 0 512 512"><circle cx="256" cy="256" r="256" fill={color} /><path fill="#fff" d="M235.472 392.08l-121.04-94.296 34.416-44.168 74.328 57.904 122.672-177.016 46.032 31.888z"/></svg>;
|
||||
}
|
||||
if (this.type.indexOf("error") >= 0 || this.type == "re-input-password") {
|
||||
if (this.type.indexOf("error") >= 0 || this.type == "re-input-password" || this.type == "session-re-login" || this.type == "session-login-re-password") {
|
||||
return <svg viewBox="0 0 512 512"><ellipse cx="256" cy="256" rx="256" ry="255.832" fill={color}/><g fill="#fff"><path d="M376.812 337.18l-39.592 39.593-201.998-201.999 39.592-39.592z"/><path d="M376.818 174.825L174.819 376.824l-39.592-39.592 201.999-201.999z"/></g></svg>;
|
||||
}
|
||||
return null;
|
||||
@@ -56,11 +56,36 @@ class MsgboxComponent: Reactor.Component {
|
||||
</div>;
|
||||
}
|
||||
|
||||
function getInputUserPasswordContent() {
|
||||
return <div .form>
|
||||
<div>{translate("OS Username")}</div>
|
||||
<div .username><input name='osusername' type='text' .outline-focus /></div>
|
||||
<div>{translate("OS Password")}</div>
|
||||
<PasswordComponent name='ospassword' />
|
||||
<div></div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function getXsessionPasswordContent() {
|
||||
return <div .form>
|
||||
<div>{translate("OS Username")}</div>
|
||||
<div .username><input name='osusername' type='text' .outline-focus /></div>
|
||||
<div>{translate("OS Password")}</div>
|
||||
<PasswordComponent name='ospassword' />
|
||||
<div>{translate('Please enter your password')}</div>
|
||||
<PasswordComponent />
|
||||
<div><button|checkbox(remember) {ts}>{translate('Remember password')}</button></div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function getContent() {
|
||||
if (this.type == "input-password") {
|
||||
return this.getInputPasswordContent();
|
||||
}
|
||||
if (this.type == "custom-os-password") {
|
||||
} else if (this.type == "session-login") {
|
||||
return this.getInputUserPasswordContent();
|
||||
} else if (this.type == "session-login-password") {
|
||||
return this.getXsessionPasswordContent();
|
||||
} else if (this.type == "custom-os-password") {
|
||||
var ts = this.auto_login ? { checked: true } : {};
|
||||
return <div .form>
|
||||
<PasswordComponent value={this.content} />
|
||||
@@ -71,13 +96,13 @@ class MsgboxComponent: Reactor.Component {
|
||||
}
|
||||
|
||||
function getColor() {
|
||||
if (this.type == "input-password" || this.type == "custom-os-password") {
|
||||
if (this.type == "input-password" || this.type == "custom-os-password" || this.type == "session-login" || this.type == "session-login-password") {
|
||||
return "#AD448E";
|
||||
}
|
||||
if (this.type == "success") {
|
||||
return "#32bea6";
|
||||
}
|
||||
if (this.type.indexOf("error") >= 0 || this.type == "re-input-password") {
|
||||
if (this.type.indexOf("error") >= 0 || this.type == "re-input-password" || this.type == "session-re-login" || this.type == "session-login-re-password") {
|
||||
return "#e04f5f";
|
||||
}
|
||||
return "#2C8CFF";
|
||||
@@ -177,6 +202,16 @@ class MsgboxComponent: Reactor.Component {
|
||||
this.update();
|
||||
return;
|
||||
}
|
||||
if (this.type == "session-re-login") {
|
||||
this.type = "session-login";
|
||||
this.update();
|
||||
return;
|
||||
}
|
||||
if (this.type == "session-login-re-password") {
|
||||
this.type = "session-login-password";
|
||||
this.update();
|
||||
return;
|
||||
}
|
||||
var values = this.getValues();
|
||||
if (this.callback) {
|
||||
var self = this;
|
||||
@@ -238,6 +273,21 @@ class MsgboxComponent: Reactor.Component {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.type == "session-login") {
|
||||
values.osusername = (values.osusername || "").trim();
|
||||
values.ospassword = (values.ospassword || "").trim();
|
||||
if (!values.osusername || !values.ospassword) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.type == "session-login-password") {
|
||||
values.password = (values.password || "").trim();
|
||||
values.osusername = (values.osusername || "").trim();
|
||||
values.ospassword = (values.ospassword || "").trim();
|
||||
if (!values.osusername || !values.ospassword || !values.password) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ use hbb_common::{
|
||||
|
||||
use crate::{
|
||||
client::*,
|
||||
ui_interface::has_hwcodec,
|
||||
ui_session_interface::{InvokeUiSession, Session},
|
||||
};
|
||||
|
||||
@@ -145,6 +144,8 @@ impl InvokeUiSession for SciterHandler {
|
||||
self.call("setConnectionType", &make_args!(is_secured, direct));
|
||||
}
|
||||
|
||||
fn set_fingerprint(&self, _fingerprint: String) {}
|
||||
|
||||
fn job_error(&self, id: i32, err: String, file_num: i32) {
|
||||
self.call("jobError", &make_args!(id, err, file_num));
|
||||
}
|
||||
@@ -197,7 +198,14 @@ impl InvokeUiSession for SciterHandler {
|
||||
self.call("confirmDeleteFiles", &make_args!(id, i, name));
|
||||
}
|
||||
|
||||
fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool, is_identical: bool) {
|
||||
fn override_file_confirm(
|
||||
&self,
|
||||
id: i32,
|
||||
file_num: i32,
|
||||
to: String,
|
||||
is_upload: bool,
|
||||
is_identical: bool,
|
||||
) {
|
||||
self.call(
|
||||
"overrideFileConfirm",
|
||||
&make_args!(id, file_num, to, is_upload, is_identical),
|
||||
@@ -398,7 +406,7 @@ impl sciter::EventHandler for SciterSession {
|
||||
fn is_file_transfer();
|
||||
fn is_port_forward();
|
||||
fn is_rdp();
|
||||
fn login(String, bool);
|
||||
fn login(String, String, String, bool);
|
||||
fn new_rdp();
|
||||
fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
|
||||
fn enter();
|
||||
@@ -451,8 +459,7 @@ impl sciter::EventHandler for SciterSession {
|
||||
fn set_write_override(i32, i32, bool, bool, bool);
|
||||
fn get_keyboard_mode();
|
||||
fn save_keyboard_mode(String);
|
||||
fn has_hwcodec();
|
||||
fn supported_hwcodec();
|
||||
fn alternative_codecs();
|
||||
fn change_prefer_codec();
|
||||
fn restart_remote_device();
|
||||
fn request_voice_call();
|
||||
@@ -504,10 +511,6 @@ impl SciterSession {
|
||||
v
|
||||
}
|
||||
|
||||
fn has_hwcodec(&self) -> bool {
|
||||
has_hwcodec()
|
||||
}
|
||||
|
||||
pub fn t(&self, name: String) -> String {
|
||||
crate::client::translate(name)
|
||||
}
|
||||
@@ -516,9 +519,10 @@ impl SciterSession {
|
||||
super::get_icon()
|
||||
}
|
||||
|
||||
fn supported_hwcodec(&self) -> Value {
|
||||
let (h264, h265) = self.0.supported_hwcodec();
|
||||
fn alternative_codecs(&self) -> Value {
|
||||
let (vp8, h264, h265) = self.0.alternative_codecs();
|
||||
let mut v = Value::array(0);
|
||||
v.push(vp8);
|
||||
v.push(h264);
|
||||
v.push(h265);
|
||||
v
|
||||
|
||||
@@ -15,7 +15,12 @@ use std::{
|
||||
use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, set_conn_enabled, ContextSend};
|
||||
use serde_derive::Serialize;
|
||||
|
||||
use crate::ipc::{self, Connection, Data};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::ipc::Connection;
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
use crate::ipc::{self, Data};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::tokio::sync::mpsc::unbounded_channel;
|
||||
#[cfg(windows)]
|
||||
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
||||
use hbb_common::{
|
||||
@@ -28,7 +33,7 @@ use hbb_common::{
|
||||
protobuf::Message as _,
|
||||
tokio::{
|
||||
self,
|
||||
sync::mpsc::{self, unbounded_channel, UnboundedSender},
|
||||
sync::mpsc::{self, UnboundedSender},
|
||||
task::spawn_blocking,
|
||||
},
|
||||
};
|
||||
@@ -52,9 +57,11 @@ pub struct Client {
|
||||
pub in_voice_call: bool,
|
||||
pub incoming_voice_call: bool,
|
||||
#[serde(skip)]
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
tx: UnboundedSender<Data>,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
struct IpcTaskRunner<T: InvokeUiCM> {
|
||||
stream: Connection,
|
||||
cm: ConnectionManager<T>,
|
||||
@@ -124,6 +131,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
restart: bool,
|
||||
recording: bool,
|
||||
from_switch: bool,
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
tx: mpsc::UnboundedSender<Data>,
|
||||
) {
|
||||
let client = Client {
|
||||
@@ -141,9 +149,10 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
restart,
|
||||
recording,
|
||||
from_switch,
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
tx,
|
||||
in_voice_call: false,
|
||||
incoming_voice_call: false
|
||||
incoming_voice_call: false,
|
||||
};
|
||||
CLIENTS
|
||||
.write()
|
||||
@@ -183,10 +192,12 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
self.ui_handler.remove_connection(id, close);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn show_elevation(&self, show: bool) {
|
||||
self.ui_handler.show_elevation(show);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn voice_call_started(&self, id: i32) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
client.incoming_voice_call = false;
|
||||
@@ -195,6 +206,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn voice_call_incoming(&self, id: i32) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
client.incoming_voice_call = true;
|
||||
@@ -203,6 +215,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn voice_call_closed(&self, id: i32, _reason: &str) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
client.incoming_voice_call = false;
|
||||
@@ -213,6 +226,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub fn check_click_time(id: i32) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::ClickTime(0)));
|
||||
@@ -225,6 +239,7 @@ pub fn get_click_time() -> i64 {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub fn authorize(id: i32) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
client.authorized = true;
|
||||
@@ -233,6 +248,7 @@ pub fn authorize(id: i32) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub fn close(id: i32) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::Close));
|
||||
@@ -246,6 +262,7 @@ pub fn remove(id: i32) {
|
||||
|
||||
// server mode send chat to peer
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub fn send_chat(id: i32, text: String) {
|
||||
let clients = CLIENTS.read().unwrap();
|
||||
if let Some(client) = clients.get(&id) {
|
||||
@@ -254,6 +271,7 @@ pub fn send_chat(id: i32, text: String) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub fn switch_permission(id: i32, name: String, enabled: bool) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::SwitchPermission { name, enabled }));
|
||||
@@ -276,12 +294,14 @@ pub fn get_clients_length() -> usize {
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub fn switch_back(id: i32) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::SwitchSidesBack));
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
#[cfg(windows)]
|
||||
async fn enable_cliprdr_file_context(&mut self, conn_id: i32, enabled: bool) {
|
||||
@@ -494,7 +514,7 @@ pub async fn start_ipc<T: InvokeUiCM>(cm: ConnectionManager<T>) {
|
||||
e
|
||||
);
|
||||
}
|
||||
allow_err!(crate::win_privacy::start());
|
||||
allow_err!(crate::privacy_win_mag::start());
|
||||
});
|
||||
|
||||
match ipc::new_listener("_cm").await {
|
||||
@@ -584,6 +604,7 @@ pub async fn start_listen<T: InvokeUiCM>(
|
||||
cm.remove_connection(current_id, true);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
async fn handle_fs(fs: ipc::FS, write_jobs: &mut Vec<fs::TransferJob>, tx: &UnboundedSender<Data>) {
|
||||
match fs {
|
||||
ipc::FS::ReadDir {
|
||||
@@ -729,6 +750,7 @@ async fn handle_fs(fs: ipc::FS, write_jobs: &mut Vec<fs::TransferJob>, tx: &Unbo
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
async fn read_dir(dir: &str, include_hidden: bool, tx: &UnboundedSender<Data>) {
|
||||
let path = {
|
||||
if dir.is_empty() {
|
||||
@@ -746,6 +768,7 @@ async fn read_dir(dir: &str, include_hidden: bool, tx: &UnboundedSender<Data>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
async fn handle_result<F: std::fmt::Display, S: std::fmt::Display>(
|
||||
res: std::result::Result<std::result::Result<(), F>, S>,
|
||||
id: i32,
|
||||
@@ -765,6 +788,7 @@ async fn handle_result<F: std::fmt::Display, S: std::fmt::Display>(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
async fn remove_file(path: String, id: i32, file_num: i32, tx: &UnboundedSender<Data>) {
|
||||
handle_result(
|
||||
spawn_blocking(move || fs::remove_file(&path)).await,
|
||||
@@ -775,6 +799,7 @@ async fn remove_file(path: String, id: i32, file_num: i32, tx: &UnboundedSender<
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
async fn create_dir(path: String, id: i32, tx: &UnboundedSender<Data>) {
|
||||
handle_result(
|
||||
spawn_blocking(move || fs::create_dir(&path)).await,
|
||||
@@ -785,6 +810,7 @@ async fn create_dir(path: String, id: i32, tx: &UnboundedSender<Data>) {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
async fn remove_dir(path: String, id: i32, recursive: bool, tx: &UnboundedSender<Data>) {
|
||||
let path = fs::get_path(&path);
|
||||
handle_result(
|
||||
@@ -803,6 +829,7 @@ async fn remove_dir(path: String, id: i32, recursive: bool, tx: &UnboundedSender
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
fn send_raw(msg: Message, tx: &UnboundedSender<Data>) {
|
||||
match msg.write_to_bytes() {
|
||||
Ok(bytes) => {
|
||||
@@ -849,6 +876,8 @@ pub fn elevate_portable(_id: i32) {
|
||||
#[inline]
|
||||
pub fn handle_incoming_voice_call(id: i32, accept: bool) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
// Not handled in iOS yet.
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
allow_err!(client.tx.send(Data::VoiceCallResponse(accept)));
|
||||
};
|
||||
}
|
||||
@@ -857,6 +886,8 @@ pub fn handle_incoming_voice_call(id: i32, accept: bool) {
|
||||
#[inline]
|
||||
pub fn close_voice_call(id: i32) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
// Not handled in iOS yet.
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
allow_err!(client.tx.send(Data::CloseVoiceCall("".to_owned())));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,8 +9,12 @@ use hbb_common::password_security;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::{self, Config, LocalConfig, PeerConfig},
|
||||
directories_next, log, sleep,
|
||||
tokio::{self, sync::mpsc, time},
|
||||
directories_next, log, tokio,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::{
|
||||
sleep,
|
||||
tokio::{sync::mpsc, time},
|
||||
};
|
||||
|
||||
use hbb_common::{
|
||||
@@ -20,9 +24,11 @@ use hbb_common::{
|
||||
rendezvous_proto::*,
|
||||
};
|
||||
|
||||
use crate::common::SOFTWARE_UPDATE_URL;
|
||||
#[cfg(feature = "flutter")]
|
||||
use crate::hbbs_http::account;
|
||||
use crate::{common::SOFTWARE_UPDATE_URL, ipc};
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
use crate::ipc;
|
||||
|
||||
type Message = RendezvousMessage;
|
||||
|
||||
@@ -339,8 +345,8 @@ pub fn get_socks() -> Vec<String> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn set_socks(proxy: String, username: String, password: String) {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
ipc::set_socks(config::Socks5Server {
|
||||
proxy,
|
||||
username,
|
||||
@@ -349,6 +355,9 @@ pub fn set_socks(proxy: String, username: String, password: String) {
|
||||
.ok();
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub fn set_socks(_: String, _: String, _: String) {}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[inline]
|
||||
pub fn is_installed() -> bool {
|
||||
@@ -505,10 +514,10 @@ pub fn get_error() -> String {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let dtype = crate::platform::linux::get_display_server();
|
||||
if "wayland" == dtype {
|
||||
if crate::platform::linux::DISPLAY_SERVER_WAYLAND == dtype {
|
||||
return crate::server::wayland::common_get_error();
|
||||
}
|
||||
if dtype != "x11" {
|
||||
if dtype != crate::platform::linux::DISPLAY_SERVER_X11 {
|
||||
return format!(
|
||||
"{} {}, {}",
|
||||
crate::client::translate("Unsupported display server".to_owned()),
|
||||
@@ -561,6 +570,7 @@ pub fn create_shortcut(_id: String) {
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
#[inline]
|
||||
pub fn discover() {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
std::thread::spawn(move || {
|
||||
allow_err!(crate::lan::discover());
|
||||
});
|
||||
@@ -745,6 +755,13 @@ pub fn has_hwcodec() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[inline]
|
||||
pub fn supported_hwdecodings() -> (bool, bool) {
|
||||
let decoding = scrap::codec::Decoder::supported_decodings(None);
|
||||
(decoding.ability_h264 > 0, decoding.ability_h265 > 0)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[inline]
|
||||
pub fn is_root() -> bool {
|
||||
@@ -791,14 +808,15 @@ pub fn check_zombie(children: Children) {
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure `SENDER` is inited here.
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn start_option_status_sync() {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
let _sender = SENDER.lock().unwrap();
|
||||
}
|
||||
let _sender = SENDER.lock().unwrap();
|
||||
}
|
||||
|
||||
// not call directly
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender<ipc::Data> {
|
||||
let (tx, rx) = mpsc::unbounded_channel::<ipc::Data>();
|
||||
std::thread::spawn(move || check_connect_status_(reconnect, rx));
|
||||
@@ -832,8 +850,20 @@ pub fn get_user_default_option(key: String) -> String {
|
||||
UserDefaultConfig::load().get(&key)
|
||||
}
|
||||
|
||||
pub fn get_fingerprint() -> String {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
if Config::get_key_confirmed() {
|
||||
return crate::common::pk_to_fingerprint(Config::get_key_pair().1);
|
||||
} else {
|
||||
return "".to_owned();
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
return ipc::get_fingerprint();
|
||||
}
|
||||
|
||||
// notice: avoiding create ipc connection repeatedly,
|
||||
// because windows named pipe has serious memory leak issue.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc::Data>) {
|
||||
let mut key_confirmed = false;
|
||||
@@ -905,7 +935,8 @@ pub fn option_synced() -> bool {
|
||||
OPTION_SYNCED.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
#[cfg(any(target_os = "android", feature = "flutter"))]
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub(crate) async fn send_to_cm(data: &ipc::Data) {
|
||||
if let Ok(mut c) = ipc::connect(1000, "_cm").await {
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
#[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::{collections::HashMap, sync::atomic::AtomicBool};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc, Mutex, RwLock,
|
||||
},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use rdev::{Event, EventType::*};
|
||||
use rdev::{Event, EventType::*, KeyCode};
|
||||
use uuid::Uuid;
|
||||
|
||||
use hbb_common::config::{Config, LocalConfig, PeerConfig};
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
use hbb_common::fs;
|
||||
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 hbb_common::{get_version_number, log, Stream};
|
||||
|
||||
use crate::client::io_loop::Remote;
|
||||
use crate::client::{
|
||||
@@ -25,10 +29,12 @@ use crate::client::{
|
||||
input_os_password, load_config, send_mouse, start_video_audio_threads, FileManager, Key,
|
||||
LoginConfigHandler, QualityStatus, KEY_MAP,
|
||||
};
|
||||
use crate::common::{self, GrabState};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::common::GrabState;
|
||||
use crate::keyboard;
|
||||
use crate::{client::Data, client::Interface};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub static IS_IN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@@ -53,6 +59,7 @@ pub struct SessionPermissionConfig {
|
||||
pub server_clipboard_enabled: Arc<RwLock<bool>>,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
impl SessionPermissionConfig {
|
||||
pub fn is_text_clipboard_required(&self) -> bool {
|
||||
*self.server_clipboard_enabled.read().unwrap()
|
||||
@@ -62,6 +69,7 @@ impl SessionPermissionConfig {
|
||||
}
|
||||
|
||||
impl<T: InvokeUiSession> Session<T> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn get_permission_config(&self) -> SessionPermissionConfig {
|
||||
SessionPermissionConfig {
|
||||
lc: self.lc.clone(),
|
||||
@@ -87,6 +95,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
.eq(&ConnType::PORT_FORWARD)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn is_rdp(&self) -> bool {
|
||||
self.lc.read().unwrap().conn_type.eq(&ConnType::RDP)
|
||||
}
|
||||
@@ -155,10 +164,12 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.lc.read().unwrap().get_toggle_option(&name)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
pub fn is_privacy_mode_supported(&self) -> bool {
|
||||
self.lc.read().unwrap().is_privacy_mode_supported()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn is_text_clipboard_required(&self) -> bool {
|
||||
*self.server_clipboard_enabled.read().unwrap()
|
||||
&& *self.server_keyboard_enabled.read().unwrap()
|
||||
@@ -198,6 +209,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.lc.read().unwrap().remember
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
pub fn set_write_override(
|
||||
&mut self,
|
||||
job_id: i32,
|
||||
@@ -216,24 +228,16 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn supported_hwcodec(&self) -> (bool, bool) {
|
||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
||||
{
|
||||
let decoder = scrap::codec::Decoder::video_codec_state(&self.id);
|
||||
let mut h264 = decoder.score_h264 > 0;
|
||||
let mut h265 = decoder.score_h265 > 0;
|
||||
let (encoding_264, encoding_265) = self
|
||||
.lc
|
||||
.read()
|
||||
.unwrap()
|
||||
.supported_encoding
|
||||
.unwrap_or_default();
|
||||
h264 = h264 && encoding_264;
|
||||
h265 = h265 && encoding_265;
|
||||
return (h264, h265);
|
||||
}
|
||||
#[allow(unreachable_code)]
|
||||
(false, false)
|
||||
pub fn alternative_codecs(&self) -> (bool, bool, bool) {
|
||||
let decoder = scrap::codec::Decoder::supported_decodings(None);
|
||||
let mut vp8 = decoder.ability_vp8 > 0;
|
||||
let mut h264 = decoder.ability_h264 > 0;
|
||||
let mut h265 = decoder.ability_h265 > 0;
|
||||
let enc = &self.lc.read().unwrap().supported_encoding;
|
||||
vp8 = vp8 && enc.vp8;
|
||||
h264 = h264 && enc.h264;
|
||||
h265 = h265 && enc.h265;
|
||||
(vp8, h264, h265)
|
||||
}
|
||||
|
||||
pub fn change_prefer_codec(&self) {
|
||||
@@ -248,6 +252,16 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn send_plugin_request(&self, request: PluginRequest) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_plugin_request(request);
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
self.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
pub fn get_audit_server(&self, typ: String) -> String {
|
||||
if self.lc.read().unwrap().conn_id <= 0
|
||||
|| LocalConfig::get_option("access_token").is_empty()
|
||||
@@ -270,13 +284,13 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn is_xfce(&self) -> bool {
|
||||
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)
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
return crate::platform::is_xfce();
|
||||
#[cfg(any(target_os = "ios"))]
|
||||
false
|
||||
}
|
||||
|
||||
pub fn remove_port_forward(&self, port: i32) {
|
||||
@@ -307,6 +321,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.send(Data::AddPortForward(pf));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
pub fn get_id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
@@ -366,6 +381,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
input_os_password(pass, activate, self.clone());
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
pub fn get_chatbox(&self) -> String {
|
||||
#[cfg(feature = "inline")]
|
||||
return crate::ui::inline::get_chatbox();
|
||||
@@ -421,7 +437,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
rdev::win_scancode_from_key(key).unwrap_or_default()
|
||||
}
|
||||
"macos" => {
|
||||
let key = rdev::macos_key_from_code(code);
|
||||
let key = rdev::macos_key_from_code(code as _);
|
||||
let key = match key {
|
||||
rdev::Key::ControlLeft => rdev::Key::MetaLeft,
|
||||
rdev::Key::MetaLeft => rdev::Key::ControlLeft,
|
||||
@@ -429,7 +445,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
rdev::Key::MetaRight => rdev::Key::ControlLeft,
|
||||
_ => key,
|
||||
};
|
||||
rdev::macos_keycode_from_key(key).unwrap_or_default()
|
||||
rdev::macos_keycode_from_key(key).unwrap_or_default() as _
|
||||
}
|
||||
_ => {
|
||||
let key = rdev::linux_key_from_code(code);
|
||||
@@ -480,6 +496,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn enter(&self) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
@@ -494,6 +511,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
keyboard::client::change_grab_status(GrabState::Run);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn leave(&self) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
@@ -534,25 +552,37 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios"))]
|
||||
pub fn handle_flutter_key_event(
|
||||
&self,
|
||||
_name: &str,
|
||||
keycode: i32,
|
||||
scancode: i32,
|
||||
platform_code: i32,
|
||||
position_code: i32,
|
||||
lock_modes: i32,
|
||||
down_or_up: bool,
|
||||
) {
|
||||
if scancode < 0 || keycode < 0 {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub fn handle_flutter_key_event(
|
||||
&self,
|
||||
_name: &str,
|
||||
platform_code: i32,
|
||||
position_code: i32,
|
||||
lock_modes: i32,
|
||||
down_or_up: bool,
|
||||
) {
|
||||
if position_code < 0 || platform_code < 0 {
|
||||
return;
|
||||
}
|
||||
let keycode: u32 = keycode as u32;
|
||||
let scancode: u32 = scancode as u32;
|
||||
let platform_code: u32 = platform_code as _;
|
||||
let position_code: KeyCode = position_code as _;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let key = rdev::key_from_code(keycode) as rdev::Key;
|
||||
let key = rdev::key_from_code(position_code) as rdev::Key;
|
||||
// Windows requires special handling
|
||||
#[cfg(target_os = "windows")]
|
||||
let key = rdev::get_win_key(keycode, scancode);
|
||||
let key = rdev::get_win_key(platform_code, position_code);
|
||||
|
||||
let event_type = if down_or_up {
|
||||
KeyPress(key)
|
||||
@@ -562,9 +592,9 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
let event = Event {
|
||||
time: SystemTime::now(),
|
||||
unicode: None,
|
||||
code: keycode as _,
|
||||
scan_code: scancode as _,
|
||||
event_type: event_type,
|
||||
platform_code,
|
||||
position_code: position_code as _,
|
||||
event_type,
|
||||
};
|
||||
keyboard::client::process_event(&event, Some(lock_modes));
|
||||
}
|
||||
@@ -661,6 +691,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
pub fn get_icon_path(&self, file_type: i32, ext: String) -> String {
|
||||
let mut path = Config::icon_path();
|
||||
if file_type == FileType::DirLink as i32 {
|
||||
@@ -711,8 +742,14 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
fs::get_string(&path)
|
||||
}
|
||||
|
||||
pub fn login(&self, password: String, remember: bool) {
|
||||
self.send(Data::Login((password, remember)));
|
||||
pub fn login(
|
||||
&self,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: String,
|
||||
remember: bool,
|
||||
) {
|
||||
self.send(Data::Login((os_username, os_password, password, remember)));
|
||||
}
|
||||
|
||||
pub fn new_rdp(&self) {
|
||||
@@ -757,6 +794,10 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.send(Data::ElevateWithLogon(username, password));
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios"))]
|
||||
pub fn switch_sides(&self) {}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn switch_sides(&self) {
|
||||
match crate::ipc::connect(1000, "").await {
|
||||
@@ -858,6 +899,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
|
||||
fn close_success(&self);
|
||||
fn update_quality_status(&self, qs: QualityStatus);
|
||||
fn set_connection_type(&self, is_secured: bool, direct: bool);
|
||||
fn set_fingerprint(&self, fingerprint: String);
|
||||
fn job_error(&self, id: i32, err: String, file_num: i32);
|
||||
fn job_done(&self, id: i32, file_num: i32);
|
||||
fn clear_all_jobs(&self);
|
||||
@@ -1005,8 +1047,23 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
handle_hash(self.lc.clone(), pass, hash, self, peer).await;
|
||||
}
|
||||
|
||||
async fn handle_login_from_ui(&mut self, password: String, remember: bool, peer: &mut Stream) {
|
||||
handle_login_from_ui(self.lc.clone(), password, remember, peer).await;
|
||||
async fn handle_login_from_ui(
|
||||
&mut self,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: String,
|
||||
remember: bool,
|
||||
peer: &mut Stream,
|
||||
) {
|
||||
handle_login_from_ui(
|
||||
self.lc.clone(),
|
||||
os_username,
|
||||
os_password,
|
||||
password,
|
||||
remember,
|
||||
peer,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {
|
||||
@@ -1053,6 +1110,9 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let (sender, receiver) = mpsc::unbounded_channel::<Data>();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let (sender, mut receiver) = mpsc::unbounded_channel::<Data>();
|
||||
*handler.sender.write().unwrap() = Some(sender.clone());
|
||||
let token = LocalConfig::get_option("access_token");
|
||||
@@ -1146,18 +1206,21 @@ 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: &mut Vec<u8>| {
|
||||
frame_count_cl.fetch_add(1, Ordering::Relaxed);
|
||||
ui_handler.on_rgba(data);
|
||||
});
|
||||
let (video_sender, audio_sender, video_queue, decode_fps) =
|
||||
start_video_audio_threads(move |data: &mut Vec<u8>| {
|
||||
frame_count_cl.fetch_add(1, Ordering::Relaxed);
|
||||
ui_handler.on_rgba(data);
|
||||
});
|
||||
|
||||
let mut remote = Remote::new(
|
||||
handler,
|
||||
video_queue,
|
||||
video_sender,
|
||||
audio_sender,
|
||||
receiver,
|
||||
sender,
|
||||
frame_count,
|
||||
decode_fps,
|
||||
);
|
||||
remote.io_loop(&key, &token).await;
|
||||
remote.sync_jobs_status_to_local().await;
|
||||
|
||||
106
src/virtual_display_manager.rs
Normal file
106
src/virtual_display_manager.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use hbb_common::{allow_err, bail, lazy_static, log, ResultType};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
// virtual display index range: 0 - 2 are reserved for headless and other special uses.
|
||||
const VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS: u32 = 0;
|
||||
const VIRTUAL_DISPLAY_START_FOR_PEER: u32 = 3;
|
||||
const VIRTUAL_DISPLAY_MAX_COUNT: u32 = 10;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref VIRTUAL_DISPLAY_MANAGER: Arc<Mutex<VirtualDisplayManager>> =
|
||||
Arc::new(Mutex::new(VirtualDisplayManager::default()));
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct VirtualDisplayManager {
|
||||
headless_index: Option<u32>,
|
||||
peer_required_indices: HashSet<u32>,
|
||||
}
|
||||
|
||||
impl VirtualDisplayManager {
|
||||
fn prepare_driver() -> ResultType<()> {
|
||||
if let Err(e) = virtual_display::create_device() {
|
||||
if !e.to_string().contains("Device is already created") {
|
||||
bail!("Create device failed {}", e);
|
||||
}
|
||||
}
|
||||
// Reboot is not required for this case.
|
||||
let mut _reboot_required = false;
|
||||
allow_err!(virtual_display::install_update_driver(
|
||||
&mut _reboot_required
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn plug_in_monitor(index: u32, modes: &[virtual_display::MonitorMode]) -> ResultType<()> {
|
||||
if let Err(e) = virtual_display::plug_in_monitor(index) {
|
||||
bail!("Plug in monitor failed {}", e);
|
||||
}
|
||||
if let Err(e) = virtual_display::update_monitor_modes(index, &modes) {
|
||||
log::error!("Update monitor modes failed {}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plug_in_headless() -> ResultType<()> {
|
||||
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
|
||||
VirtualDisplayManager::prepare_driver()?;
|
||||
let modes = [virtual_display::MonitorMode {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
sync: 60,
|
||||
}];
|
||||
VirtualDisplayManager::plug_in_monitor(VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, &modes)?;
|
||||
manager.headless_index = Some(VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn plug_out_headless() {
|
||||
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
|
||||
if let Some(index) = manager.headless_index.take() {
|
||||
if let Err(e) = virtual_display::plug_out_monitor(index) {
|
||||
log::error!("Plug out monitor failed {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plug_in_peer_required(
|
||||
modes: Vec<Vec<virtual_display::MonitorMode>>,
|
||||
) -> ResultType<Vec<u32>> {
|
||||
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
|
||||
VirtualDisplayManager::prepare_driver()?;
|
||||
|
||||
let mut indices: Vec<u32> = Vec::new();
|
||||
for m in modes.iter() {
|
||||
for idx in VIRTUAL_DISPLAY_START_FOR_PEER..VIRTUAL_DISPLAY_MAX_COUNT {
|
||||
if !manager.peer_required_indices.contains(&idx) {
|
||||
match VirtualDisplayManager::plug_in_monitor(idx, m) {
|
||||
Ok(_) => {
|
||||
manager.peer_required_indices.insert(idx);
|
||||
indices.push(idx);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Plug in monitor failed {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(indices)
|
||||
}
|
||||
|
||||
pub fn plug_out_peer_required(modes: &[u32]) -> ResultType<()> {
|
||||
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
|
||||
for idx in modes.iter() {
|
||||
if manager.peer_required_indices.contains(idx) {
|
||||
allow_err!(virtual_display::plug_out_monitor(*idx));
|
||||
manager.peer_required_indices.remove(idx);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user