mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge branch 'rustdesk/master'
This commit is contained in:
303
src/client.rs
303
src/client.rs
@@ -12,19 +12,18 @@ use cpal::{
|
||||
Device, Host, StreamConfig,
|
||||
};
|
||||
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
|
||||
use scrap::{
|
||||
codec::{Decoder, DecoderCfg},
|
||||
VpxDecoderConfig, VpxVideoCodecId,
|
||||
};
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use file_trait::FileManager;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::{anyhow, Context},
|
||||
bail,
|
||||
config::{Config, PeerConfig, PeerInfoSerde, CONNECT_TIMEOUT, RELAY_PORT, RENDEZVOUS_TIMEOUT},
|
||||
config::{
|
||||
Config, PeerConfig, PeerInfoSerde, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT,
|
||||
RENDEZVOUS_TIMEOUT,
|
||||
},
|
||||
log,
|
||||
message_proto::{option_message::BoolOption, *},
|
||||
protobuf::Message as _,
|
||||
@@ -36,14 +35,22 @@ use hbb_common::{
|
||||
tokio::time::Duration,
|
||||
AddrMangle, ResultType, Stream,
|
||||
};
|
||||
pub use helper::LatencyController;
|
||||
pub use helper::*;
|
||||
use scrap::Image;
|
||||
use scrap::{
|
||||
codec::{Decoder, DecoderCfg},
|
||||
VpxDecoderConfig, VpxVideoCodecId,
|
||||
};
|
||||
|
||||
pub use super::lang::*;
|
||||
|
||||
pub mod file_trait;
|
||||
pub use file_trait::FileManager;
|
||||
pub mod helper;
|
||||
pub use helper::*;
|
||||
|
||||
pub const SEC30: Duration = Duration::from_secs(30);
|
||||
|
||||
/// Client of the remote desktop.
|
||||
pub struct Client;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
@@ -111,13 +118,15 @@ impl Drop for OboePlayer {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Start a new connection.
|
||||
pub async fn start(
|
||||
peer: &str,
|
||||
key: &str,
|
||||
token: &str,
|
||||
conn_type: ConnType,
|
||||
interface: impl Interface,
|
||||
) -> ResultType<(Stream, bool)> {
|
||||
match Self::_start(peer, key, token, conn_type).await {
|
||||
match Self::_start(peer, key, token, conn_type, interface).await {
|
||||
Err(err) => {
|
||||
let err_str = err.to_string();
|
||||
if err_str.starts_with("Failed") {
|
||||
@@ -130,11 +139,13 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a new connection.
|
||||
async fn _start(
|
||||
peer: &str,
|
||||
key: &str,
|
||||
token: &str,
|
||||
conn_type: ConnType,
|
||||
interface: impl Interface,
|
||||
) -> ResultType<(Stream, bool)> {
|
||||
// to-do: remember the port for each peer, so that we can retry easier
|
||||
let any_addr = Config::get_any_listen_addr();
|
||||
@@ -181,7 +192,11 @@ impl Client {
|
||||
log::info!("#{} punch attempt with {}, id: {}", i, my_addr, peer);
|
||||
let mut msg_out = RendezvousMessage::new();
|
||||
use hbb_common::protobuf::Enum;
|
||||
let nat_type = NatType::from_i32(my_nat_type).unwrap_or(NatType::UNKNOWN_NAT);
|
||||
let nat_type = if interface.is_force_relay() {
|
||||
NatType::SYMMETRIC
|
||||
} else {
|
||||
NatType::from_i32(my_nat_type).unwrap_or(NatType::UNKNOWN_NAT)
|
||||
};
|
||||
msg_out.set_punch_hole_request(PunchHoleRequest {
|
||||
id: peer.to_owned(),
|
||||
token: token.to_owned(),
|
||||
@@ -233,7 +248,15 @@ impl Client {
|
||||
let mut conn =
|
||||
Self::create_relay(peer, rr.uuid, rr.relay_server, key, conn_type)
|
||||
.await?;
|
||||
Self::secure_connection(peer, signed_id_pk, key, &mut conn).await?;
|
||||
Self::secure_connection(
|
||||
peer,
|
||||
signed_id_pk,
|
||||
key,
|
||||
&mut conn,
|
||||
false,
|
||||
interface,
|
||||
)
|
||||
.await?;
|
||||
return Ok((conn, false));
|
||||
}
|
||||
_ => {
|
||||
@@ -274,10 +297,12 @@ impl Client {
|
||||
key,
|
||||
token,
|
||||
conn_type,
|
||||
interface,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Connect to the peer.
|
||||
async fn connect(
|
||||
local_addr: SocketAddr,
|
||||
peer: SocketAddr,
|
||||
@@ -292,6 +317,7 @@ impl Client {
|
||||
key: &str,
|
||||
token: &str,
|
||||
conn_type: ConnType,
|
||||
interface: impl Interface,
|
||||
) -> ResultType<(Stream, bool)> {
|
||||
let direct_failures = PeerConfig::load(peer_id).direct_failures;
|
||||
let mut connect_timeout = 0;
|
||||
@@ -329,8 +355,8 @@ impl Client {
|
||||
let start = std::time::Instant::now();
|
||||
// NOTICE: Socks5 is be used event in intranet. Which may be not a good way.
|
||||
let mut conn = socket_client::connect_tcp(peer, local_addr, connect_timeout).await;
|
||||
let direct = !conn.is_err();
|
||||
if conn.is_err() {
|
||||
let mut direct = !conn.is_err();
|
||||
if interface.is_force_relay() || conn.is_err() {
|
||||
if !relay_server.is_empty() {
|
||||
conn = Self::request_relay(
|
||||
peer_id,
|
||||
@@ -348,6 +374,7 @@ impl Client {
|
||||
conn.err().unwrap()
|
||||
);
|
||||
}
|
||||
direct = false;
|
||||
} else {
|
||||
bail!("Failed to make direct connection to remote desktop");
|
||||
}
|
||||
@@ -360,15 +387,18 @@ 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).await?;
|
||||
Self::secure_connection(peer_id, signed_id_pk, key, &mut conn, direct, interface).await?;
|
||||
Ok((conn, direct))
|
||||
}
|
||||
|
||||
/// Establish secure connection with the server.
|
||||
async fn secure_connection(
|
||||
peer_id: &str,
|
||||
signed_id_pk: Vec<u8>,
|
||||
key: &str,
|
||||
conn: &mut Stream,
|
||||
direct: bool,
|
||||
mut interface: impl Interface,
|
||||
) -> ResultType<()> {
|
||||
let rs_pk = get_rs_pk(if key.is_empty() {
|
||||
hbb_common::config::RS_PUB_KEY
|
||||
@@ -394,9 +424,15 @@ impl Client {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
match timeout(CONNECT_TIMEOUT, conn.next()).await? {
|
||||
match timeout(READ_TIMEOUT, conn.next()).await? {
|
||||
Some(res) => {
|
||||
let bytes = res?;
|
||||
let bytes = match res {
|
||||
Ok(bytes) => bytes,
|
||||
Err(err) => {
|
||||
interface.set_force_relay(direct, false);
|
||||
bail!("{}", err);
|
||||
}
|
||||
};
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
|
||||
if let Some(message::Union::SignedId(si)) = msg_in.union {
|
||||
if let Ok((id, their_pk_b)) = decode_id_pk(&si.id, &sign_pk) {
|
||||
@@ -441,6 +477,7 @@ impl Client {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Request a relay connection to the server.
|
||||
async fn request_relay(
|
||||
peer: &str,
|
||||
relay_server: String,
|
||||
@@ -497,6 +534,7 @@ impl Client {
|
||||
Self::create_relay(peer, uuid, relay_server, key, conn_type).await
|
||||
}
|
||||
|
||||
/// Create a relay connection to the server.
|
||||
async fn create_relay(
|
||||
peer: &str,
|
||||
uuid: String,
|
||||
@@ -524,6 +562,7 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
/// Audio handler for the [`Client`].
|
||||
#[derive(Default)]
|
||||
pub struct AudioHandler {
|
||||
audio_decoder: Option<(AudioDecoder, Vec<f32>)>,
|
||||
@@ -541,6 +580,7 @@ pub struct AudioHandler {
|
||||
}
|
||||
|
||||
impl AudioHandler {
|
||||
/// Create a new audio handler.
|
||||
pub fn new(latency_controller: Arc<Mutex<LatencyController>>) -> Self {
|
||||
AudioHandler {
|
||||
latency_controller,
|
||||
@@ -548,6 +588,7 @@ impl AudioHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the audio playback.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn start_audio(&mut self, format0: AudioFormat) -> ResultType<()> {
|
||||
use psimple::Simple;
|
||||
@@ -577,6 +618,7 @@ impl AudioHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start the audio playback.
|
||||
#[cfg(target_os = "android")]
|
||||
fn start_audio(&mut self, format0: AudioFormat) -> ResultType<()> {
|
||||
self.oboe = Some(OboePlayer::new(
|
||||
@@ -587,6 +629,7 @@ impl AudioHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start the audio playback.
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
fn start_audio(&mut self, format0: AudioFormat) -> ResultType<()> {
|
||||
let device = AUDIO_HOST
|
||||
@@ -611,6 +654,7 @@ impl AudioHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle audio format and create an audio decoder.
|
||||
pub fn handle_format(&mut self, f: AudioFormat) {
|
||||
match AudioDecoder::new(f.sample_rate, if f.channels > 1 { Stereo } else { Mono }) {
|
||||
Ok(d) => {
|
||||
@@ -625,6 +669,7 @@ impl AudioHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle audio frame and play it.
|
||||
pub fn handle_frame(&mut self, frame: AudioFrame) {
|
||||
if frame.timestamp != 0 {
|
||||
if self
|
||||
@@ -692,6 +737,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>(
|
||||
&mut self,
|
||||
@@ -727,6 +773,7 @@ impl AudioHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Video handler for the [`Client`].
|
||||
pub struct VideoHandler {
|
||||
decoder: Decoder,
|
||||
latency_controller: Arc<Mutex<LatencyController>>,
|
||||
@@ -734,6 +781,7 @@ pub struct VideoHandler {
|
||||
}
|
||||
|
||||
impl VideoHandler {
|
||||
/// Create a new video handler.
|
||||
pub fn new(latency_controller: Arc<Mutex<LatencyController>>) -> Self {
|
||||
VideoHandler {
|
||||
decoder: Decoder::new(DecoderCfg {
|
||||
@@ -747,8 +795,10 @@ impl VideoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a new video frame.
|
||||
pub fn handle_frame(&mut self, vf: VideoFrame) -> ResultType<bool> {
|
||||
if vf.timestamp != 0 {
|
||||
// Update the lantency controller with the latest timestamp.
|
||||
self.latency_controller
|
||||
.lock()
|
||||
.unwrap()
|
||||
@@ -760,6 +810,28 @@ impl VideoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a VP9S frame.
|
||||
// pub fn handle_vp9s(&mut self, vp9s: &VP9s) -> ResultType<bool> {
|
||||
// let mut last_frame = Image::new();
|
||||
// for vp9 in vp9s.frames.iter() {
|
||||
// for frame in self.decoder.decode(&vp9.data)? {
|
||||
// drop(last_frame);
|
||||
// last_frame = frame;
|
||||
// }
|
||||
// }
|
||||
// for frame in self.decoder.flush()? {
|
||||
// drop(last_frame);
|
||||
// last_frame = frame;
|
||||
// }
|
||||
// if last_frame.is_null() {
|
||||
// Ok(false)
|
||||
// } else {
|
||||
// last_frame.rgb(1, true, &mut self.rgb);
|
||||
// Ok(true)
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Reset the decoder.
|
||||
pub fn reset(&mut self) {
|
||||
self.decoder = Decoder::new(DecoderCfg {
|
||||
vpx: VpxDecoderConfig {
|
||||
@@ -770,6 +842,7 @@ impl VideoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Login config handler for [`Client`].
|
||||
#[derive(Default)]
|
||||
pub struct LoginConfigHandler {
|
||||
id: String,
|
||||
@@ -786,6 +859,7 @@ pub struct LoginConfigHandler {
|
||||
session_id: u64,
|
||||
pub supported_encoding: Option<(bool, bool)>,
|
||||
pub restarting_remote_device: bool,
|
||||
pub force_relay: bool,
|
||||
}
|
||||
|
||||
impl Deref for LoginConfigHandler {
|
||||
@@ -796,12 +870,24 @@ impl Deref for LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load [`PeerConfig`] from id.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id` - id of peer
|
||||
#[inline]
|
||||
pub fn load_config(id: &str) -> PeerConfig {
|
||||
PeerConfig::load(id)
|
||||
}
|
||||
|
||||
impl LoginConfigHandler {
|
||||
/// Initialize the login config handler.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id` - id of peer
|
||||
/// * `is_file_transfer` - Whether the connection is file transfer.
|
||||
/// * `is_port_forward` - Whether the connection is port forward.
|
||||
pub fn initialize(&mut self, id: String, is_file_transfer: bool, is_port_forward: bool) {
|
||||
self.id = id;
|
||||
self.is_file_transfer = is_file_transfer;
|
||||
@@ -812,8 +898,11 @@ impl LoginConfigHandler {
|
||||
self.session_id = rand::random();
|
||||
self.supported_encoding = None;
|
||||
self.restarting_remote_device = false;
|
||||
self.force_relay = !self.get_option("force-always-relay").is_empty();
|
||||
}
|
||||
|
||||
/// Check if the client should auto login.
|
||||
/// Return password if the client should auto login, otherwise return empty string.
|
||||
pub fn should_auto_login(&self) -> String {
|
||||
let l = self.lock_after_session_end;
|
||||
let a = !self.get_option("auto-login").is_empty();
|
||||
@@ -825,27 +914,49 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load [`PeerConfig`].
|
||||
fn load_config(&self) -> PeerConfig {
|
||||
load_config(&self.id)
|
||||
}
|
||||
|
||||
/// Save a [`PeerConfig`] into the handler.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - [`PeerConfig`] to save.
|
||||
pub fn save_config(&mut self, config: PeerConfig) {
|
||||
config.store(&self.id);
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
/// Set an option for handler's [`PeerConfig`].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `k` - key of option
|
||||
/// * `v` - value of option
|
||||
pub fn set_option(&mut self, k: String, v: String) {
|
||||
let mut config = self.load_config();
|
||||
config.options.insert(k, v);
|
||||
self.save_config(config);
|
||||
}
|
||||
|
||||
/// Save view style to the current config.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - The view style to be saved.
|
||||
pub fn save_view_style(&mut self, value: String) {
|
||||
let mut config = self.load_config();
|
||||
config.view_style = value;
|
||||
self.save_config(config);
|
||||
}
|
||||
|
||||
/// Toggle an option in the handler.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the option to toggle.
|
||||
pub fn toggle_option(&mut self, name: String) -> Option<Message> {
|
||||
let mut option = OptionMessage::default();
|
||||
let mut config = self.load_config();
|
||||
@@ -923,6 +1034,19 @@ impl LoginConfigHandler {
|
||||
Some(msg_out)
|
||||
}
|
||||
|
||||
/// Get [`PeerConfig`] of the current [`LoginConfigHandler`].
|
||||
///
|
||||
/// # Arguments
|
||||
pub fn get_config(&mut self) -> &mut PeerConfig {
|
||||
&mut self.config
|
||||
}
|
||||
|
||||
/// Get [`OptionMessage`] of the current [`LoginConfigHandler`].
|
||||
/// Return `None` if there's no option, for example, when the session is only for file transfer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ignore_default` - If `true`, ignore the default value of the option.
|
||||
fn get_option_message(&self, ignore_default: bool) -> Option<OptionMessage> {
|
||||
if self.is_port_forward || self.is_file_transfer {
|
||||
return None;
|
||||
@@ -986,6 +1110,13 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the image quality option.
|
||||
/// Return [`ImageQuality`] if the option is valid, otherwise return `None`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `q` - The image quality option.
|
||||
/// * `ignore_default` - Ignore the default value.
|
||||
fn get_image_quality_enum(&self, q: &str, ignore_default: bool) -> Option<ImageQuality> {
|
||||
if q == "low" {
|
||||
Some(ImageQuality::Low)
|
||||
@@ -1002,6 +1133,11 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the status of a toggle option.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the toggle option.
|
||||
pub fn get_toggle_option(&self, name: &str) -> bool {
|
||||
if name == "show-remote-cursor" {
|
||||
self.config.show_remote_cursor
|
||||
@@ -1030,6 +1166,7 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`Message`] for refreshing video.
|
||||
pub fn refresh() -> Message {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_refresh_video(true);
|
||||
@@ -1038,6 +1175,12 @@ impl LoginConfigHandler {
|
||||
msg_out
|
||||
}
|
||||
|
||||
/// Create a [`Message`] for saving custom image quality.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `bitrate` - The given bitrate.
|
||||
/// * `quantizer` - The given quantizer.
|
||||
pub fn save_custom_image_quality(&mut self, image_quality: i32) -> Message {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_option(OptionMessage {
|
||||
@@ -1053,6 +1196,11 @@ impl LoginConfigHandler {
|
||||
msg_out
|
||||
}
|
||||
|
||||
/// Save the given image quality to the config.
|
||||
/// Return a [`Message`] that contains image quality, or `None` if the image quality is not valid.
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - The image quality.
|
||||
pub fn save_image_quality(&mut self, value: String) -> Option<Message> {
|
||||
let mut res = None;
|
||||
if let Some(q) = self.get_image_quality_enum(&value, false) {
|
||||
@@ -1079,6 +1227,8 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle login error.
|
||||
/// Return true if the password is wrong, return false if there's an actual error.
|
||||
pub fn handle_login_error(&mut self, err: &str, interface: &impl Interface) -> bool {
|
||||
if err == "Wrong Password" {
|
||||
self.password = Default::default();
|
||||
@@ -1090,6 +1240,12 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get user name.
|
||||
/// Return the name of the given peer. If the peer has no name, return the name in the config.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pi` - peer info.
|
||||
pub fn get_username(&self, pi: &PeerInfo) -> String {
|
||||
return if pi.username.is_empty() {
|
||||
self.info.username.clone()
|
||||
@@ -1098,6 +1254,12 @@ impl LoginConfigHandler {
|
||||
};
|
||||
}
|
||||
|
||||
/// Handle peer info.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `username` - The name of the peer.
|
||||
/// * `pi` - The peer info.
|
||||
pub fn handle_peer_info(&mut self, username: String, pi: PeerInfo) {
|
||||
if !pi.version.is_empty() {
|
||||
self.version = hbb_common::get_version_number(&pi.version);
|
||||
@@ -1152,9 +1314,10 @@ impl LoginConfigHandler {
|
||||
serde_json::to_string::<HashMap<String, String>>(&x).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Create a [`Message`] for login.
|
||||
fn create_login_msg(&self, password: Vec<u8>) -> Message {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let my_id = Config::get_id_or(crate::common::MOBILE_INFO1.lock().unwrap().clone());
|
||||
let my_id = Config::get_id_or(crate::common::DEVICE_ID.lock().unwrap().clone());
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let my_id = Config::get_id();
|
||||
let mut lr = LoginRequest {
|
||||
@@ -1206,6 +1369,7 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Media data.
|
||||
pub enum MediaData {
|
||||
VideoFrame(VideoFrame),
|
||||
AudioFrame(AudioFrame),
|
||||
@@ -1215,6 +1379,12 @@ pub enum MediaData {
|
||||
|
||||
pub type MediaSender = mpsc::Sender<MediaData>;
|
||||
|
||||
/// Start video and audio thread.
|
||||
/// Return two [`MediaSender`], they should be given to the media producer.
|
||||
///
|
||||
/// # 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)
|
||||
where
|
||||
F: 'static + FnMut(&[u8]) + Send,
|
||||
@@ -1271,6 +1441,12 @@ where
|
||||
return (video_sender, audio_sender);
|
||||
}
|
||||
|
||||
/// Handle latency test.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `t` - The latency test message.
|
||||
/// * `peer` - The peer.
|
||||
pub async fn handle_test_delay(t: TestDelay, peer: &mut Stream) {
|
||||
if !t.from_client {
|
||||
let mut msg_out = Message::new();
|
||||
@@ -1279,9 +1455,21 @@ pub async fn handle_test_delay(t: TestDelay, peer: &mut Stream) {
|
||||
}
|
||||
}
|
||||
|
||||
// mask = buttons << 3 | type
|
||||
// type, 1: down, 2: up, 3: wheel
|
||||
// buttons, 1: left, 2: right, 4: middle
|
||||
/// Send mouse data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mask` - Mouse event.
|
||||
/// * mask = buttons << 3 | type
|
||||
/// * type, 1: down, 2: up, 3: wheel
|
||||
/// * buttons, 1: left, 2: right, 4: middle
|
||||
/// * `x` - X coordinate.
|
||||
/// * `y` - Y coordinate.
|
||||
/// * `alt` - Whether the alt key is pressed.
|
||||
/// * `ctrl` - Whether the ctrl key is pressed.
|
||||
/// * `shift` - Whether the shift key is pressed.
|
||||
/// * `command` - Whether the command key is pressed.
|
||||
/// * `interface` - The interface for sending data.
|
||||
#[inline]
|
||||
pub fn send_mouse(
|
||||
mask: i32,
|
||||
@@ -1316,6 +1504,11 @@ pub fn send_mouse(
|
||||
interface.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
/// Avtivate OS by sending mouse movement.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `interface` - The interface for sending data.
|
||||
fn activate_os(interface: &impl Interface) {
|
||||
send_mouse(0, 0, 0, false, false, false, false, interface);
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
@@ -1334,12 +1527,26 @@ fn activate_os(interface: &impl Interface) {
|
||||
*/
|
||||
}
|
||||
|
||||
/// Input the OS's password.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `p` - The password.
|
||||
/// * `avtivate` - Whether to activate OS.
|
||||
/// * `interface` - The interface for sending data.
|
||||
pub fn input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
std::thread::spawn(move || {
|
||||
_input_os_password(p, activate, interface);
|
||||
});
|
||||
}
|
||||
|
||||
/// Input the OS's password.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `p` - The password.
|
||||
/// * `avtivate` - Whether to activate OS.
|
||||
/// * `interface` - The interface for sending data.
|
||||
fn _input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
if activate {
|
||||
activate_os(&interface);
|
||||
@@ -1356,6 +1563,15 @@ fn _input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
interface.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
/// Handle hash message sent by peer.
|
||||
/// Hash will be used for login.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `lc` - Login config.
|
||||
/// * `hash` - Hash sent by peer.
|
||||
/// * `interface` - [`Interface`] for sending data.
|
||||
/// * `peer` - [`Stream`] for communicating with peer.
|
||||
pub async fn handle_hash(
|
||||
lc: Arc<RwLock<LoginConfigHandler>>,
|
||||
password_preset: &str,
|
||||
@@ -1389,11 +1605,26 @@ pub async fn handle_hash(
|
||||
lc.write().unwrap().hash = hash;
|
||||
}
|
||||
|
||||
/// Send login message to peer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `lc` - Login config.
|
||||
/// * `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);
|
||||
allow_err!(peer.send(&msg_out).await);
|
||||
}
|
||||
|
||||
/// Handle login request made from ui.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `lc` - Login config.
|
||||
/// * `password` - Password.
|
||||
/// * `remember` - Whether to remember password.
|
||||
/// * `peer` - [`Stream`] for communicating with peer.
|
||||
pub async fn handle_login_from_ui(
|
||||
lc: Arc<RwLock<LoginConfigHandler>>,
|
||||
password: String,
|
||||
@@ -1412,24 +1643,28 @@ pub async fn handle_login_from_ui(
|
||||
send_login(lc.clone(), hasher2.finalize()[..].into(), peer).await;
|
||||
}
|
||||
|
||||
/// Interface for client to send data and commands.
|
||||
#[async_trait]
|
||||
pub trait Interface: Send + Clone + 'static + Sized {
|
||||
fn send(&self, data: Data);
|
||||
fn msgbox(&self, msgtype: &str, title: &str, text: &str);
|
||||
fn handle_login_error(&mut self, err: &str) -> bool;
|
||||
fn handle_peer_info(&mut self, pi: PeerInfo);
|
||||
fn set_force_relay(&mut self, direct: bool, received: bool);
|
||||
fn is_force_relay(&self) -> bool;
|
||||
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_test_delay(&mut self, t: TestDelay, peer: &mut Stream);
|
||||
}
|
||||
|
||||
/// Data used by the client interface.
|
||||
#[derive(Clone)]
|
||||
pub enum Data {
|
||||
Close,
|
||||
Login((String, bool)),
|
||||
Message(Message),
|
||||
SendFiles((i32, String, String, i32, bool, bool)),
|
||||
RemoveDirAll((i32, String, bool)),
|
||||
RemoveDirAll((i32, String, bool, bool)),
|
||||
ConfirmDeleteFiles((i32, i32)),
|
||||
SetNoConfirm(i32),
|
||||
RemoveDir((i32, String)),
|
||||
@@ -1445,6 +1680,7 @@ pub enum Data {
|
||||
ResumeJob((i32, bool)),
|
||||
}
|
||||
|
||||
/// Keycode for key events.
|
||||
#[derive(Clone)]
|
||||
pub enum Key {
|
||||
ControlKey(ControlKey),
|
||||
@@ -1575,18 +1811,27 @@ lazy_static::lazy_static! {
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
/// Check if the given message is an error and can be retried.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `msgtype` - The message type.
|
||||
/// * `title` - The title of the message.
|
||||
/// * `text` - The text of the message.
|
||||
#[inline]
|
||||
pub fn check_if_retry(msgtype: &str, title: &str, text: &str) -> bool {
|
||||
msgtype == "error"
|
||||
&& title == "Connection Error"
|
||||
&& !text.to_lowercase().contains("offline")
|
||||
&& !text.to_lowercase().contains("exist")
|
||||
&& !text.to_lowercase().contains("handshake")
|
||||
&& !text.to_lowercase().contains("failed")
|
||||
&& !text.to_lowercase().contains("resolve")
|
||||
&& !text.to_lowercase().contains("mismatch")
|
||||
&& !text.to_lowercase().contains("manually")
|
||||
&& !text.to_lowercase().contains("not allowed")
|
||||
&& (text.contains("10054")
|
||||
|| text.contains("104")
|
||||
|| (!text.to_lowercase().contains("offline")
|
||||
&& !text.to_lowercase().contains("exist")
|
||||
&& !text.to_lowercase().contains("handshake")
|
||||
&& !text.to_lowercase().contains("failed")
|
||||
&& !text.to_lowercase().contains("resolve")
|
||||
&& !text.to_lowercase().contains("mismatch")
|
||||
&& !text.to_lowercase().contains("manually")
|
||||
&& !text.to_lowercase().contains("not allowed")))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use hbb_common::{fs, message_proto::*};
|
||||
|
||||
use super::{Data, Interface};
|
||||
use hbb_common::{
|
||||
fs,
|
||||
message_proto::*,
|
||||
};
|
||||
|
||||
pub trait FileManager: Interface {
|
||||
fn get_home_dir(&self) -> String{
|
||||
fn get_home_dir(&self) -> String {
|
||||
fs::get_home_as_string()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
|
||||
fn read_dir(&self,path: String, include_hidden: bool) -> sciter::Value {
|
||||
fn read_dir(&self, path: String, include_hidden: bool) -> sciter::Value {
|
||||
match fs::read_dir(&fs::get_path(&path), include_hidden) {
|
||||
Err(_) => sciter::Value::null(),
|
||||
Ok(fd) => {
|
||||
@@ -23,15 +21,15 @@ pub trait FileManager: Interface {
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
fn read_dir(&self,path: &str, include_hidden: bool) -> String {
|
||||
fn read_dir(&self, path: &str, include_hidden: bool) -> String {
|
||||
use crate::common::make_fd_to_json;
|
||||
match fs::read_dir(&fs::get_path(path), include_hidden){
|
||||
match fs::read_dir(&fs::get_path(path), include_hidden) {
|
||||
Ok(fd) => make_fd_to_json(fd),
|
||||
Err(_)=>"".into()
|
||||
Err(_) => "".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_job(&mut self, id: i32) {
|
||||
fn cancel_job(&self, id: i32) {
|
||||
self.send(Data::CancelJob(id));
|
||||
}
|
||||
|
||||
@@ -47,23 +45,23 @@ pub trait FileManager: Interface {
|
||||
self.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
fn remove_file(&mut self, id: i32, path: String, file_num: i32, is_remote: bool) {
|
||||
fn remove_file(&self, id: i32, path: String, file_num: i32, is_remote: bool) {
|
||||
self.send(Data::RemoveFile((id, path, file_num, is_remote)));
|
||||
}
|
||||
|
||||
fn remove_dir_all(&mut self, id: i32, path: String, is_remote: bool) {
|
||||
self.send(Data::RemoveDirAll((id, path, is_remote)));
|
||||
fn remove_dir_all(&self, id: i32, path: String, is_remote: bool, include_hidden: bool) {
|
||||
self.send(Data::RemoveDirAll((id, path, is_remote, include_hidden)));
|
||||
}
|
||||
|
||||
fn confirm_delete_files(&mut self, id: i32, file_num: i32) {
|
||||
fn confirm_delete_files(&self, id: i32, file_num: i32) {
|
||||
self.send(Data::ConfirmDeleteFiles((id, file_num)));
|
||||
}
|
||||
|
||||
fn set_no_confirm(&mut self, id: i32) {
|
||||
fn set_no_confirm(&self, id: i32) {
|
||||
self.send(Data::SetNoConfirm(id));
|
||||
}
|
||||
|
||||
fn remove_dir(&mut self, id: i32, path: String, is_remote: bool) {
|
||||
fn remove_dir(&self, id: i32, path: String, is_remote: bool) {
|
||||
if is_remote {
|
||||
self.send(Data::RemoveDir((id, path)));
|
||||
} else {
|
||||
@@ -71,12 +69,12 @@ pub trait FileManager: Interface {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_dir(&mut self, id: i32, path: String, is_remote: bool) {
|
||||
fn create_dir(&self, id: i32, path: String, is_remote: bool) {
|
||||
self.send(Data::CreateDir((id, path, is_remote)));
|
||||
}
|
||||
|
||||
fn send_files(
|
||||
&mut self,
|
||||
&self,
|
||||
id: i32,
|
||||
path: String,
|
||||
to: String,
|
||||
@@ -84,11 +82,18 @@ pub trait FileManager: Interface {
|
||||
include_hidden: bool,
|
||||
is_remote: bool,
|
||||
) {
|
||||
self.send(Data::SendFiles((id, path, to, file_num, include_hidden, is_remote)));
|
||||
self.send(Data::SendFiles((
|
||||
id,
|
||||
path,
|
||||
to,
|
||||
file_num,
|
||||
include_hidden,
|
||||
is_remote,
|
||||
)));
|
||||
}
|
||||
|
||||
fn add_job(
|
||||
&mut self,
|
||||
&self,
|
||||
id: i32,
|
||||
path: String,
|
||||
to: String,
|
||||
@@ -96,10 +101,17 @@ pub trait FileManager: Interface {
|
||||
include_hidden: bool,
|
||||
is_remote: bool,
|
||||
) {
|
||||
self.send(Data::AddJob((id, path, to, file_num, include_hidden, is_remote)));
|
||||
self.send(Data::AddJob((
|
||||
id,
|
||||
path,
|
||||
to,
|
||||
file_num,
|
||||
include_hidden,
|
||||
is_remote,
|
||||
)));
|
||||
}
|
||||
|
||||
fn resume_job(&mut self, id: i32, is_remote: bool){
|
||||
self.send(Data::ResumeJob((id,is_remote)));
|
||||
fn resume_job(&self, id: i32, is_remote: bool) {
|
||||
self.send(Data::ResumeJob((id, is_remote)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ use hbb_common::{
|
||||
const MAX_LATENCY: i64 = 500;
|
||||
const MIN_LATENCY: i64 = 100;
|
||||
|
||||
// based on video frame time, fix audio latency relatively.
|
||||
// only works on audio, can't fix video latency.
|
||||
/// 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 deivce
|
||||
@@ -31,21 +31,23 @@ impl Default for LatencyController {
|
||||
}
|
||||
|
||||
impl LatencyController {
|
||||
/// Create a new latency controller.
|
||||
pub fn new() -> Arc<Mutex<LatencyController>> {
|
||||
Arc::new(Mutex::new(LatencyController::default()))
|
||||
}
|
||||
|
||||
// first, receive new video frame and update time
|
||||
/// 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();
|
||||
}
|
||||
|
||||
// second, compute audio latency
|
||||
// set MAX and MIN, avoid fixing too frequently.
|
||||
/// 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 = 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);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use arboard::Clipboard as ClipboardContext;
|
||||
use serde_json::json;
|
||||
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::bail,
|
||||
@@ -7,13 +11,13 @@ use hbb_common::{
|
||||
config::{self, Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT},
|
||||
get_version_number, log,
|
||||
message_proto::*,
|
||||
protobuf::Message as _,
|
||||
protobuf::Enum,
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::*,
|
||||
sleep, socket_client, tokio, ResultType,
|
||||
};
|
||||
// #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub const CLIPBOARD_NAME: &'static str = "clipboard";
|
||||
pub const CLIPBOARD_INTERVAL: u64 = 333;
|
||||
@@ -23,10 +27,9 @@ lazy_static::lazy_static! {
|
||||
pub static ref SOFTWARE_UPDATE_URL: Arc<Mutex<String>> = Default::default();
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref MOBILE_INFO1: Arc<Mutex<String>> = Default::default();
|
||||
pub static ref MOBILE_INFO2: Arc<Mutex<String>> = Default::default();
|
||||
pub static ref DEVICE_ID: Arc<Mutex<String>> = Default::default();
|
||||
pub static ref DEVICE_NAME: Arc<Mutex<String>> = Default::default();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -48,7 +51,7 @@ pub fn create_clipboard_msg(content: String) -> Message {
|
||||
let mut msg = Message::new();
|
||||
msg.set_clipboard(Clipboard {
|
||||
compress,
|
||||
content:content.into(),
|
||||
content: content.into(),
|
||||
..Default::default()
|
||||
});
|
||||
msg
|
||||
@@ -101,6 +104,19 @@ pub fn update_clipboard(clipboard: Clipboard, old: Option<&Arc<Mutex<String>>>)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_opts_after_login(
|
||||
config: &crate::client::LoginConfigHandler,
|
||||
peer: &mut hbb_common::tcp::FramedStream,
|
||||
) {
|
||||
if let Some(opts) = config.get_option_message_after_login() {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_option(opts);
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
allow_err!(peer.send(&msg_out).await);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "use_rubato")]
|
||||
pub fn resample_channels(
|
||||
data: &[f32],
|
||||
@@ -367,6 +383,7 @@ pub async fn get_nat_type(ms_timeout: u64) -> i32 {
|
||||
crate::ipc::get_nat_type(ms_timeout).await
|
||||
}
|
||||
|
||||
// #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn test_rendezvous_server_() {
|
||||
let servers = Config::get_rendezvous_servers();
|
||||
@@ -393,6 +410,7 @@ async fn test_rendezvous_server_() {
|
||||
join_all(futs).await;
|
||||
}
|
||||
|
||||
// #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
pub fn test_rendezvous_server() {
|
||||
std::thread::spawn(test_rendezvous_server_);
|
||||
}
|
||||
@@ -436,7 +454,7 @@ pub fn username() -> String {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
return whoami::username().trim_end_matches('\0').to_owned();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
return MOBILE_INFO2.lock().unwrap().clone();
|
||||
return DEVICE_NAME.lock().unwrap().clone();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -667,3 +685,30 @@ pub fn make_fd_to_json(fd: FileDirectory) -> String {
|
||||
fd_json.insert("entries".into(), json!(entries));
|
||||
serde_json::to_string(&fd_json).unwrap_or("".into())
|
||||
}
|
||||
|
||||
pub fn make_fd_flutter(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> String {
|
||||
let mut m = serde_json::Map::new();
|
||||
m.insert("id".into(), json!(id));
|
||||
let mut a = vec![];
|
||||
let mut n: u64 = 0;
|
||||
for entry in entries {
|
||||
n += entry.size;
|
||||
if only_count {
|
||||
continue;
|
||||
}
|
||||
let mut e = serde_json::Map::new();
|
||||
e.insert("name".into(), json!(entry.name.to_owned()));
|
||||
let tmp = entry.entry_type.value();
|
||||
e.insert("type".into(), json!(if tmp == 0 { 1 } else { tmp }));
|
||||
e.insert("time".into(), json!(entry.modified_time as f64));
|
||||
e.insert("size".into(), json!(entry.size as f64));
|
||||
a.push(e);
|
||||
}
|
||||
if only_count {
|
||||
m.insert("num_entries".into(), json!(entries.len() as i32));
|
||||
} else {
|
||||
m.insert("entries".into(), json!(a));
|
||||
}
|
||||
m.insert("total_size".into(), json!(n as f64));
|
||||
serde_json::to_string(&m).unwrap_or("".into())
|
||||
}
|
||||
|
||||
28
src/core_main.rs
Normal file
28
src/core_main.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use hbb_common::log;
|
||||
|
||||
use crate::{start_os_service, flutter::connection_manager};
|
||||
|
||||
/// Main entry of the RustDesk Core.
|
||||
/// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit.
|
||||
pub fn core_main() -> bool {
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
// TODO: implement core_main()
|
||||
if args.len() > 1 {
|
||||
if args[1] == "--cm" {
|
||||
// call connection manager to establish connections
|
||||
// meanwhile, return true to call flutter window to show control panel
|
||||
connection_manager::start_listen_ipc_thread();
|
||||
return true;
|
||||
}
|
||||
if args[1] == "--service" {
|
||||
log::info!("start --service");
|
||||
start_os_service();
|
||||
return false;
|
||||
}
|
||||
if args[1] == "--server" {
|
||||
// TODO: server
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
849
src/flutter_ffi.rs
Normal file
849
src/flutter_ffi.rs
Normal file
@@ -0,0 +1,849 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{CStr, CString},
|
||||
os::raw::c_char,
|
||||
};
|
||||
|
||||
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
|
||||
use serde_json::{json, Number, Value};
|
||||
|
||||
use hbb_common::{
|
||||
config::{self, Config, LocalConfig, PeerConfig, ONLINE},
|
||||
fs, log,
|
||||
};
|
||||
use hbb_common::{password_security, ResultType};
|
||||
|
||||
use crate::client::file_trait::FileManager;
|
||||
use crate::common::make_fd_to_json;
|
||||
use crate::flutter::connection_manager::{self, get_clients_length, get_clients_state};
|
||||
use crate::flutter::{self, Session, SESSIONS};
|
||||
use crate::start_server;
|
||||
use crate::ui_interface;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::ui_interface::{change_id, check_connect_status, is_ok_change_id};
|
||||
use crate::ui_interface::{
|
||||
check_super_user_permission, discover, forget_password, get_api_server, get_app_name,
|
||||
get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers, get_langs,
|
||||
get_license, get_local_option, get_option, get_options, get_peer, get_peer_option, get_socks,
|
||||
get_sound_inputs, get_uuid, get_version, has_hwcodec, has_rendezvous_service, post_request,
|
||||
set_local_option, set_option, set_options, set_peer_option, set_permanent_password, set_socks,
|
||||
store_fav, test_if_valid_server, update_temporary_password, using_public_server,
|
||||
};
|
||||
|
||||
fn initialize(app_dir: &str) {
|
||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||
#[cfg(feature = "cli")]
|
||||
{
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
crate::common::test_rendezvous_server();
|
||||
crate::common::test_nat_type();
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
android_logger::init_once(
|
||||
android_logger::Config::default()
|
||||
.with_min_level(log::Level::Debug) // limit log level
|
||||
.with_tag("ffi"), // logs will show under mytag tag
|
||||
);
|
||||
}
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
use hbb_common::env_logger::*;
|
||||
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "debug"));
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
crate::common::check_software_update();
|
||||
}
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
use hbb_common::env_logger::*;
|
||||
if let Err(e) = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "debug")) {
|
||||
log::debug!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI for rustdesk core's main entry.
|
||||
/// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rustdesk_core_main() -> bool {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
return crate::core_main::core_main();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
false
|
||||
}
|
||||
|
||||
pub enum EventToUI {
|
||||
Event(String),
|
||||
Rgba(ZeroCopyBuffer<Vec<u8>>),
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
pub fn session_connect(
|
||||
events2ui: StreamSink<EventToUI>,
|
||||
id: String,
|
||||
is_file_transfer: bool,
|
||||
) -> ResultType<()> {
|
||||
Session::start(&id, is_file_transfer, events2ui);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn session_get_remember(id: String) -> Option<bool> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
Some(session.get_remember())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_toggle_option(id: String, arg: String) -> Option<bool> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
Some(session.get_toggle_option(&arg))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_toggle_option_sync(id: String, arg: String) -> SyncReturn<bool> {
|
||||
let res = session_get_toggle_option(id, arg) == Some(true);
|
||||
SyncReturn(res)
|
||||
}
|
||||
|
||||
pub fn session_get_image_quality(id: String) -> Option<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
Some(session.get_image_quality())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_option(id: String, arg: String) -> Option<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
Some(session.get_option(&arg))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_login(id: String, password: String, remember: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.login(&password, remember);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_close(id: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.close();
|
||||
}
|
||||
let _ = SESSIONS.write().unwrap().remove(&id);
|
||||
}
|
||||
|
||||
pub fn session_refresh(id: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_reconnect(id: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_toggle_option(id: String, value: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.toggle_option(&value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_image_quality(id: String, value: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.set_image_quality(&value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_lock_screen(id: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.lock_screen();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_ctrl_alt_del(id: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.ctrl_alt_del();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_switch_display(id: String, value: i32) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.switch_display(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_input_key(
|
||||
id: String,
|
||||
name: String,
|
||||
down: bool,
|
||||
press: bool,
|
||||
alt: bool,
|
||||
ctrl: bool,
|
||||
shift: bool,
|
||||
command: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.input_key(&name, down, press, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_input_string(id: String, value: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.input_string(&value);
|
||||
}
|
||||
}
|
||||
|
||||
// chat_client_mode
|
||||
pub fn session_send_chat(id: String, text: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.send_chat(text);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_peer_option(id: String, name: String, value: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.set_option(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_peer_option(id: String, name: String) -> String {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
return session.get_option(&name);
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn session_input_os_password(id: String, value: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.input_os_password(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
// File Action
|
||||
pub fn session_read_remote_dir(id: String, path: String, include_hidden: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.read_remote_dir(path, include_hidden);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_send_files(
|
||||
id: String,
|
||||
act_id: i32,
|
||||
path: String,
|
||||
to: String,
|
||||
file_num: i32,
|
||||
include_hidden: bool,
|
||||
is_remote: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.send_files(act_id, path, to, file_num, include_hidden, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_confirm_override_file(
|
||||
id: String,
|
||||
act_id: i32,
|
||||
file_num: i32,
|
||||
need_override: bool,
|
||||
remember: bool,
|
||||
is_upload: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.set_confirm_override_file(act_id, file_num, need_override, remember, is_upload);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_remove_file(id: String, act_id: i32, path: String, file_num: i32, is_remote: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.remove_file(act_id, path, file_num, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_read_dir_recursive(
|
||||
id: String,
|
||||
act_id: i32,
|
||||
path: String,
|
||||
is_remote: bool,
|
||||
show_hidden: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.remove_dir_all(act_id, path, is_remote, show_hidden);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_remove_all_empty_dirs(id: String, act_id: i32, path: String, is_remote: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.remove_dir(act_id, path, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_cancel_job(id: String, act_id: i32) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.cancel_job(act_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_create_dir(id: String, act_id: i32, path: String, is_remote: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.create_dir(act_id, path, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_read_local_dir_sync(id: String, path: String, show_hidden: bool) -> String {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
if let Ok(fd) = fs::read_dir(&fs::get_path(&path), show_hidden) {
|
||||
return make_fd_to_json(fd);
|
||||
}
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn session_get_platform(id: String, is_remote: bool) -> String {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
return session.get_platform(is_remote);
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn session_load_last_transfer_jobs(id: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
return session.load_last_jobs();
|
||||
} else {
|
||||
// a tip for flutter dev
|
||||
eprintln!(
|
||||
"cannot load last transfer job from non-existed session. Please ensure session \
|
||||
is connected before calling load last transfer jobs."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_add_job(
|
||||
id: String,
|
||||
act_id: i32,
|
||||
path: String,
|
||||
to: String,
|
||||
file_num: i32,
|
||||
include_hidden: bool,
|
||||
is_remote: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.add_job(act_id, path, to, file_num, include_hidden, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_resume_job(id: String, act_id: i32, is_remote: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.resume_job(act_id, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_get_sound_inputs() -> Vec<String> {
|
||||
get_sound_inputs()
|
||||
}
|
||||
|
||||
pub fn main_change_id(new_id: String) {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
change_id(new_id)
|
||||
}
|
||||
|
||||
pub fn main_get_async_status() -> String {
|
||||
get_async_job_status()
|
||||
}
|
||||
|
||||
pub fn main_get_option(key: String) -> String {
|
||||
get_option(key)
|
||||
}
|
||||
|
||||
pub fn main_set_option(key: String, value: String) {
|
||||
if key.eq("custom-rendezvous-server") {
|
||||
set_option(key, value);
|
||||
#[cfg(target_os = "android")]
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
crate::common::test_rendezvous_server();
|
||||
} else {
|
||||
set_option(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_get_options() -> String {
|
||||
get_options()
|
||||
}
|
||||
|
||||
pub fn main_set_options(json: String) {
|
||||
let map: HashMap<String, String> = serde_json::from_str(&json).unwrap_or(HashMap::new());
|
||||
if !map.is_empty() {
|
||||
set_options(map)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_test_if_valid_server(server: String) -> String {
|
||||
test_if_valid_server(server)
|
||||
}
|
||||
|
||||
pub fn main_set_socks(proxy: String, username: String, password: String) {
|
||||
set_socks(proxy, username, password)
|
||||
}
|
||||
|
||||
pub fn main_get_socks() -> Vec<String> {
|
||||
get_socks()
|
||||
}
|
||||
|
||||
pub fn main_get_app_name() -> String {
|
||||
get_app_name()
|
||||
}
|
||||
|
||||
pub fn main_get_license() -> String {
|
||||
get_license()
|
||||
}
|
||||
|
||||
pub fn main_get_version() -> String {
|
||||
get_version()
|
||||
}
|
||||
|
||||
pub fn main_get_fav() -> Vec<String> {
|
||||
get_fav()
|
||||
}
|
||||
|
||||
pub fn main_store_fav(favs: Vec<String>) {
|
||||
store_fav(favs)
|
||||
}
|
||||
|
||||
pub fn main_get_peer(id: String) -> String {
|
||||
let conf = get_peer(id);
|
||||
serde_json::to_string(&conf).unwrap_or("".to_string())
|
||||
}
|
||||
|
||||
pub fn main_get_lan_peers() -> String {
|
||||
serde_json::to_string(&get_lan_peers()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn main_get_connect_status() -> String {
|
||||
let status = get_connect_status();
|
||||
// (status_num, key_confirmed, mouse_time, id)
|
||||
let mut m = serde_json::Map::new();
|
||||
m.insert("status_num".to_string(), json!(status.0));
|
||||
m.insert("key_confirmed".to_string(), json!(status.1));
|
||||
m.insert("mouse_time".to_string(), json!(status.2));
|
||||
m.insert("id".to_string(), json!(status.3));
|
||||
serde_json::to_string(&m).unwrap_or("".to_string())
|
||||
}
|
||||
|
||||
pub fn main_check_connect_status() {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
check_connect_status(true);
|
||||
}
|
||||
|
||||
pub fn main_is_using_public_server() -> bool {
|
||||
using_public_server()
|
||||
}
|
||||
|
||||
pub fn main_discover() {
|
||||
discover();
|
||||
}
|
||||
|
||||
pub fn main_has_rendezvous_service() -> bool {
|
||||
has_rendezvous_service()
|
||||
}
|
||||
|
||||
pub fn main_get_api_server() -> String {
|
||||
get_api_server()
|
||||
}
|
||||
|
||||
pub fn main_post_request(url: String, body: String, header: String) {
|
||||
post_request(url, body, header)
|
||||
}
|
||||
|
||||
pub fn main_get_local_option(key: String) -> String {
|
||||
get_local_option(key)
|
||||
}
|
||||
|
||||
pub fn main_set_local_option(key: String, value: String) {
|
||||
set_local_option(key, value)
|
||||
}
|
||||
|
||||
pub fn main_get_my_id() -> String {
|
||||
get_id()
|
||||
}
|
||||
|
||||
pub fn main_get_uuid() -> String {
|
||||
get_uuid()
|
||||
}
|
||||
|
||||
pub fn main_get_peer_option(id: String, key: String) -> String {
|
||||
get_peer_option(id, key)
|
||||
}
|
||||
|
||||
pub fn main_set_peer_option(id: String, key: String, value: String) {
|
||||
set_peer_option(id, key, value)
|
||||
}
|
||||
|
||||
pub fn main_forget_password(id: String) {
|
||||
forget_password(id)
|
||||
}
|
||||
|
||||
// TODO APP_DIR & ui_interface
|
||||
pub fn main_get_recent_peers() -> String {
|
||||
if !config::APP_DIR.read().unwrap().is_empty() {
|
||||
let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers()
|
||||
.drain(..)
|
||||
.map(|(id, _, p)| (id, p.info))
|
||||
.collect();
|
||||
serde_json::ser::to_string(&peers).unwrap_or("".to_owned())
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_load_recent_peers() {
|
||||
if !config::APP_DIR.read().unwrap().is_empty() {
|
||||
let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers()
|
||||
.drain(..)
|
||||
.map(|(id, _, p)| (id, p.info))
|
||||
.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()));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_load_fav_peers() {
|
||||
if !config::APP_DIR.read().unwrap().is_empty() {
|
||||
let favs = get_fav();
|
||||
let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers()
|
||||
.into_iter()
|
||||
.filter_map(|(id, _, peer)| {
|
||||
if favs.contains(&id) {
|
||||
Some((id, peer.info))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.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()));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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()));
|
||||
};
|
||||
}
|
||||
|
||||
pub fn main_get_last_remote_id() -> String {
|
||||
// if !config::APP_DIR.read().unwrap().is_empty() {
|
||||
// res = LocalConfig::get_remote_id();
|
||||
// }
|
||||
LocalConfig::get_remote_id()
|
||||
}
|
||||
|
||||
pub fn main_get_software_update_url() -> String {
|
||||
crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn main_get_home_dir() -> String {
|
||||
fs::get_home_as_string()
|
||||
}
|
||||
|
||||
pub fn main_get_langs() -> String {
|
||||
get_langs()
|
||||
}
|
||||
|
||||
pub fn main_get_temporary_password() -> String {
|
||||
ui_interface::temporary_password()
|
||||
}
|
||||
|
||||
pub fn main_get_permanent_password() -> String {
|
||||
ui_interface::permanent_password()
|
||||
}
|
||||
|
||||
pub fn main_get_online_statue() -> i64 {
|
||||
ONLINE.lock().unwrap().values().max().unwrap_or(&0).clone()
|
||||
}
|
||||
|
||||
pub fn main_get_clients_state() -> String {
|
||||
get_clients_state()
|
||||
}
|
||||
|
||||
pub fn main_check_clients_length(length: usize) -> Option<String> {
|
||||
if length != get_clients_length() {
|
||||
Some(get_clients_state())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_init(app_dir: String) {
|
||||
initialize(&app_dir);
|
||||
}
|
||||
|
||||
pub fn main_device_id(id: String) {
|
||||
*crate::common::DEVICE_ID.lock().unwrap() = id;
|
||||
}
|
||||
|
||||
pub fn main_device_name(name: String) {
|
||||
*crate::common::DEVICE_NAME.lock().unwrap() = name;
|
||||
}
|
||||
|
||||
pub fn main_remove_peer(id: String) {
|
||||
PeerConfig::remove(&id);
|
||||
}
|
||||
|
||||
pub fn main_has_hwcodec() -> bool {
|
||||
has_hwcodec()
|
||||
}
|
||||
|
||||
// TODO
|
||||
pub fn session_send_mouse(id: String, msg: String) {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(&msg) {
|
||||
let alt = m.get("alt").is_some();
|
||||
let ctrl = m.get("ctrl").is_some();
|
||||
let shift = m.get("shift").is_some();
|
||||
let command = m.get("command").is_some();
|
||||
let x = m
|
||||
.get("x")
|
||||
.map(|x| x.parse::<i32>().unwrap_or(0))
|
||||
.unwrap_or(0);
|
||||
let y = m
|
||||
.get("y")
|
||||
.map(|x| x.parse::<i32>().unwrap_or(0))
|
||||
.unwrap_or(0);
|
||||
let mut mask = 0;
|
||||
if let Some(_type) = m.get("type") {
|
||||
mask = match _type.as_str() {
|
||||
"down" => 1,
|
||||
"up" => 2,
|
||||
"wheel" => 3,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
if let Some(buttons) = m.get("buttons") {
|
||||
mask |= match buttons.as_str() {
|
||||
"left" => 1,
|
||||
"right" => 2,
|
||||
"wheel" => 4,
|
||||
_ => 0,
|
||||
} << 3;
|
||||
}
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.send_mouse(mask, x, y, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_restart_remote_device(id: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.restart_remote_device();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_set_home_dir(home: String) {
|
||||
*config::APP_HOME_DIR.write().unwrap() = home;
|
||||
}
|
||||
|
||||
pub fn main_stop_service() {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
Config::set_option("stop-service".into(), "Y".into());
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_start_service() {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
Config::set_option("stop-service".into(), "".into());
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
std::thread::spawn(move || start_server(true));
|
||||
}
|
||||
|
||||
pub fn main_update_temporary_password() {
|
||||
update_temporary_password();
|
||||
}
|
||||
|
||||
pub fn main_set_permanent_password(password: String) {
|
||||
set_permanent_password(password);
|
||||
}
|
||||
|
||||
pub fn main_check_super_user_permission() -> bool {
|
||||
check_super_user_permission()
|
||||
}
|
||||
|
||||
pub fn cm_send_chat(conn_id: i32, msg: String) {
|
||||
connection_manager::send_chat(conn_id, msg);
|
||||
}
|
||||
|
||||
pub fn cm_login_res(conn_id: i32, res: bool) {
|
||||
connection_manager::on_login_res(conn_id, res);
|
||||
}
|
||||
|
||||
pub fn cm_close_connection(conn_id: i32) {
|
||||
connection_manager::close_conn(conn_id);
|
||||
}
|
||||
|
||||
pub fn cm_check_click_time(conn_id: i32) {
|
||||
connection_manager::check_click_time(conn_id)
|
||||
}
|
||||
|
||||
pub fn cm_get_click_time() -> f64 {
|
||||
connection_manager::get_click_time() as _
|
||||
}
|
||||
|
||||
pub fn cm_switch_permission(conn_id: i32, name: String, enabled: bool) {
|
||||
connection_manager::switch_permission(conn_id, name, enabled)
|
||||
}
|
||||
|
||||
pub fn main_get_icon() -> String {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
|
||||
return ui_interface::get_icon();
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
return String::new();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *const c_char {
|
||||
let name = CStr::from_ptr(name);
|
||||
let locale = CStr::from_ptr(locale);
|
||||
let res = if let (Ok(name), Ok(locale)) = (name.to_str(), locale.to_str()) {
|
||||
crate::client::translate_locale(name.to_owned(), locale)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
CString::from_vec_unchecked(res.into_bytes()).into_raw()
|
||||
}
|
||||
|
||||
fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) {
|
||||
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()));
|
||||
};
|
||||
}
|
||||
|
||||
pub fn query_onlines(ids: Vec<String>) {
|
||||
crate::rendezvous_mediator::query_online_states(ids, handle_query_onlines)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use jni::{
|
||||
objects::{JClass, JString},
|
||||
sys::jstring,
|
||||
JNIEnv,
|
||||
};
|
||||
|
||||
use hbb_common::{config::Config, log};
|
||||
|
||||
use crate::start_server;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
) {
|
||||
log::debug!("startServer from java");
|
||||
std::thread::spawn(move || start_server(true));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
locale: JString,
|
||||
input: JString,
|
||||
) -> jstring {
|
||||
let res = if let (Ok(input), Ok(locale)) = (env.get_string(input), env.get_string(locale)) {
|
||||
let input: String = input.into();
|
||||
let locale: String = locale.into();
|
||||
crate::client::translate_locale(input, &locale)
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
return env.new_string(res).unwrap_or(input).into_inner();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_refreshScreen(
|
||||
_env: JNIEnv,
|
||||
_class: JClass,
|
||||
) {
|
||||
crate::server::video_service::refresh()
|
||||
}
|
||||
}
|
||||
96
src/ipc.rs
96
src/ipc.rs
@@ -1,5 +1,13 @@
|
||||
use crate::rendezvous_mediator::RendezvousMediator;
|
||||
use std::{collections::HashMap, sync::atomic::Ordering};
|
||||
#[cfg(not(windows))]
|
||||
use std::{fs::File, io::prelude::*};
|
||||
|
||||
use bytes::Bytes;
|
||||
use parity_tokio_ipc::{
|
||||
Connection as Conn, ConnectionClient as ConnClient, Endpoint, Incoming, SecurityAttributes,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use clipboard::ClipbaordFile;
|
||||
use hbb_common::{
|
||||
@@ -13,13 +21,8 @@ use hbb_common::{
|
||||
tokio_util::codec::Framed,
|
||||
ResultType,
|
||||
};
|
||||
use parity_tokio_ipc::{
|
||||
Connection as Conn, ConnectionClient as ConnClient, Endpoint, Incoming, SecurityAttributes,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, sync::atomic::Ordering};
|
||||
#[cfg(not(windows))]
|
||||
use std::{fs::File, io::prelude::*};
|
||||
|
||||
use crate::rendezvous_mediator::RendezvousMediator;
|
||||
|
||||
// State with timestamp, because std::time::Instant cannot be serialized
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
@@ -410,6 +413,83 @@ pub async fn connect(ms_timeout: u64, postfix: &str) -> ResultType<ConnectionTmp
|
||||
Ok(ConnectionTmpl::new(client))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_pa() {
|
||||
use crate::audio_service::AUDIO_DATA_SIZE_U8;
|
||||
|
||||
match new_listener("_pa").await {
|
||||
Ok(mut incoming) => {
|
||||
loop {
|
||||
if let Some(result) = incoming.next().await {
|
||||
match result {
|
||||
Ok(stream) => {
|
||||
let mut stream = Connection::new(stream);
|
||||
let mut device: String = "".to_owned();
|
||||
if let Some(Ok(Some(Data::Config((_, Some(x)))))) =
|
||||
stream.next_timeout2(1000).await
|
||||
{
|
||||
device = x;
|
||||
}
|
||||
if !device.is_empty() {
|
||||
device = crate::platform::linux::get_pa_source_name(&device);
|
||||
}
|
||||
if device.is_empty() {
|
||||
device = crate::platform::linux::get_pa_monitor();
|
||||
}
|
||||
if device.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let spec = pulse::sample::Spec {
|
||||
format: pulse::sample::Format::F32le,
|
||||
channels: 2,
|
||||
rate: crate::platform::PA_SAMPLE_RATE,
|
||||
};
|
||||
log::info!("pa monitor: {:?}", device);
|
||||
// systemctl --user status pulseaudio.service
|
||||
let mut buf: Vec<u8> = vec![0; AUDIO_DATA_SIZE_U8];
|
||||
match psimple::Simple::new(
|
||||
None, // Use the default server
|
||||
&crate::get_app_name(), // Our application’s name
|
||||
pulse::stream::Direction::Record, // We want a record stream
|
||||
Some(&device), // Use the default device
|
||||
"record", // Description of our stream
|
||||
&spec, // Our sample format
|
||||
None, // Use default channel map
|
||||
None, // Use default buffering attributes
|
||||
) {
|
||||
Ok(s) => loop {
|
||||
if let Ok(_) = s.read(&mut buf) {
|
||||
let out =
|
||||
if buf.iter().filter(|x| **x != 0).next().is_none() {
|
||||
vec![]
|
||||
} else {
|
||||
buf.clone()
|
||||
};
|
||||
if let Err(err) = stream.send_raw(out.into()).await {
|
||||
log::error!("Failed to send audio data:{}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("Could not create simple pulse: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Couldn't get pa client: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to start pa ipc server: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(windows))]
|
||||
fn get_pid_file(postfix: &str) -> String {
|
||||
|
||||
@@ -276,6 +276,8 @@ async fn handle_received_peers(mut rx: UnboundedReceiver<config::DiscoveryPeer>)
|
||||
peers.insert(0, peer);
|
||||
if last_write_time.elapsed().as_millis() > 300 {
|
||||
config::LanPeers::store(&peers);
|
||||
#[cfg(feature = "flutter")]
|
||||
crate::flutter_ffi::main_load_lan_peers();
|
||||
last_write_time = Instant::now();
|
||||
}
|
||||
}
|
||||
@@ -287,5 +289,7 @@ async fn handle_received_peers(mut rx: UnboundedReceiver<config::DiscoveryPeer>)
|
||||
}
|
||||
|
||||
config::LanPeers::store(&peers);
|
||||
#[cfg(feature = "flutter")]
|
||||
crate::flutter_ffi::main_load_lan_peers();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "原始比例"),
|
||||
("Shrink", "收缩"),
|
||||
("Stretch", "伸展"),
|
||||
("Scrollbar", "滚动条"),
|
||||
("ScrollAuto", "自动滚动"),
|
||||
("Good image quality", "好画质"),
|
||||
("Balanced", "一般画质"),
|
||||
("Optimize reaction time", "优化反应时间"),
|
||||
@@ -302,5 +304,6 @@ 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", "已复制"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Původní"),
|
||||
("Shrink", "Oříznout"),
|
||||
("Stretch", "Roztáhnout"),
|
||||
("Scrollbar", "Posuvník"),
|
||||
("ScrollAuto", "Rolovať Auto"),
|
||||
("Good image quality", "Dobrá kvalita obrazu"),
|
||||
("Balanced", "Vyvážené"),
|
||||
("Optimize reaction time", "Optimalizovat pro co nejnižší prodlevu odezvy"),
|
||||
@@ -302,5 +304,6 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Original"),
|
||||
("Shrink", "Krymp"),
|
||||
("Stretch", "Strak"),
|
||||
("Scrollbar", "Rullebar"),
|
||||
("ScrollAuto", "Rul Auto"),
|
||||
("Good image quality", "God billedkvalitet"),
|
||||
("Balanced", "Afbalanceret"),
|
||||
("Optimize reaction time", "Optimeret responstid"),
|
||||
@@ -302,5 +304,6 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Original"),
|
||||
("Shrink", "Verkleinern"),
|
||||
("Stretch", "Strecken"),
|
||||
("Scrollbar", "Scrollleiste"),
|
||||
("ScrollAuto", "Automatisch scrollen"),
|
||||
("Good image quality", "Schöner"),
|
||||
("Balanced", "Ausgeglichen"),
|
||||
("Optimize reaction time", "Schneller"),
|
||||
@@ -302,5 +304,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to restart", "Möchten Sie das entfernte Gerät wirklich neu starten?"),
|
||||
("Restarting Remote Device", "Entferntes Gerät wird neu gestartet"),
|
||||
("remote_restarting_tip", "Entferntes Gerät startet neu, bitte schließen Sie diese Meldung und verbinden Sie sich mit dem dauerhaften Passwort erneut."),
|
||||
("Copied", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Originala rilatumo"),
|
||||
("Shrink", "Ŝrumpi"),
|
||||
("Stretch", "Streĉi"),
|
||||
("Scrollbar", "Rulumbreto"),
|
||||
("ScrollAuto", "Rulumu Aŭtomate"),
|
||||
("Good image quality", "Bona bilda kvalito"),
|
||||
("Balanced", "Normala bilda kvalito"),
|
||||
("Optimize reaction time", "Optimigi reakcia tempo"),
|
||||
@@ -302,5 +304,6 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Original"),
|
||||
("Shrink", "Encogerse"),
|
||||
("Stretch", "Estirar"),
|
||||
("Scrollbar", "Barra de desplazamiento"),
|
||||
("ScrollAuto", "Desplazamiento automático"),
|
||||
("Good image quality", "Buena calidad de imagen"),
|
||||
("Balanced", "Equilibrado"),
|
||||
("Optimize reaction time", "Optimizar el tiempo de reacción"),
|
||||
@@ -315,5 +317,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to restart", "Esta Seguro que desea reiniciar?"),
|
||||
("Restarting Remote Device", "Reiniciando dispositivo remoto"),
|
||||
("remote_restarting_tip", "Dispositivo remoto reiniciando, favor de cerrar este mensaje y reconectarse con la contraseña permamente despues de un momento."),
|
||||
("Copied", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Ratio d'origine"),
|
||||
("Shrink", "Rétrécir"),
|
||||
("Stretch", "Étirer"),
|
||||
("Scrollbar", "Barre de défilement"),
|
||||
("ScrollAuto", "Défilement automatique"),
|
||||
("Good image quality", "Bonne qualité d'image"),
|
||||
("Balanced", "Qualité d'image normale"),
|
||||
("Optimize reaction time", "Optimiser le temps de réaction"),
|
||||
@@ -302,5 +304,6 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Eredeti"),
|
||||
("Shrink", "Zsugorított"),
|
||||
("Stretch", "Nyújtott"),
|
||||
("Scrollbar", "Görgetősáv"),
|
||||
("ScrollAuto", "Görgessen Auto"),
|
||||
("Good image quality", "Jó képminőség"),
|
||||
("Balanced", "Balanszolt"),
|
||||
("Optimize reaction time", "Válaszidő optimializálása"),
|
||||
@@ -302,5 +304,6 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Original"),
|
||||
("Shrink", "Susutkan"),
|
||||
("Stretch", "Regangkan"),
|
||||
("Scrollbar", "Scroll bar"),
|
||||
("ScrollAuto", "Gulir Otomatis"),
|
||||
("Good image quality", "Kualitas Gambar Baik"),
|
||||
("Balanced", "Seimbang"),
|
||||
("Optimize reaction time", "Optimalkan waktu reaksi"),
|
||||
@@ -315,5 +317,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to restart", "Apakah Anda yakin untuk memulai ulang"),
|
||||
("Restarting Remote Device", "Memulai Ulang Perangkat Jarak Jauh"),
|
||||
("remote_restarting_tip", ""),
|
||||
("Copied", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -101,8 +101,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Unblock user input", "Sbloccare l'input dell'utente"),
|
||||
("Adjust Window", "Adatta la finestra"),
|
||||
("Original", "Originale"),
|
||||
("Shrink", "Scala"),
|
||||
("Stretch", "Adatta"),
|
||||
("Shrink", "Restringi"),
|
||||
("Stretch", "Allarga"),
|
||||
("Scrollbar", "Barra di scorrimento"),
|
||||
("ScrollAuto", "Scorri automaticamente"),
|
||||
("Good image quality", "Buona qualità immagine"),
|
||||
("Balanced", "Bilanciato"),
|
||||
("Optimize reaction time", "Ottimizza il tempo di reazione"),
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Oryginał"),
|
||||
("Shrink", "Zmniejsz"),
|
||||
("Stretch", "Zwiększ"),
|
||||
("Scrollbar", "Pasek przewijania"),
|
||||
("ScrollAuto", "Przewijanie automatyczne"),
|
||||
("Good image quality", "Dobra jakość obrazu"),
|
||||
("Balanced", "Zrównoważony"),
|
||||
("Optimize reaction time", "Zoptymalizuj czas reakcji"),
|
||||
@@ -300,5 +302,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Activate onetime password", "Aktywuj hasło jednorazowe"),
|
||||
("Set security password", "Ustaw hasło zabezpieczające"),
|
||||
("Connection not allowed", "Połączenie niedozwolone"),
|
||||
("Copied", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Original"),
|
||||
("Shrink", "Reduzir"),
|
||||
("Stretch", "Aumentar"),
|
||||
("Scrollbar", "Barra de rolagem"),
|
||||
("ScrollAuto", "Rolagem automática"),
|
||||
("Good image quality", "Qualidade visual boa"),
|
||||
("Balanced", "Balanceada"),
|
||||
("Optimize reaction time", "Otimizar tempo de reação"),
|
||||
@@ -302,5 +304,6 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Оригинал"),
|
||||
("Shrink", "Уменьшить"),
|
||||
("Stretch", "Растянуть"),
|
||||
("Scrollbar", "Полоса прокрутки"),
|
||||
("ScrollAuto", "Прокрутка Авто"),
|
||||
("Good image quality", "Хорошее качество изображения"),
|
||||
("Balanced", "Сбалансированный"),
|
||||
("Optimize reaction time", "Оптимизировать время реакции"),
|
||||
@@ -302,5 +304,6 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Pôvodný"),
|
||||
("Shrink", "Zmenšené"),
|
||||
("Stretch", "Roztiahnuté"),
|
||||
("Scrollbar", "Posuvník"),
|
||||
("ScrollAuto", "Rolovať Auto"),
|
||||
("Good image quality", "Dobrá kvalita obrazu"),
|
||||
("Balanced", "Vyvážené"),
|
||||
("Optimize reaction time", "Optimalizované pre čas odozvy"),
|
||||
@@ -302,5 +304,6 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", ""),
|
||||
("Shrink", ""),
|
||||
("Stretch", ""),
|
||||
("Scrollbar", ""),
|
||||
("ScrollAuto", ""),
|
||||
("Good image quality", ""),
|
||||
("Balanced", ""),
|
||||
("Optimize reaction time", ""),
|
||||
@@ -302,5 +304,6 @@ 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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Orjinal"),
|
||||
("Shrink", "Küçült"),
|
||||
("Stretch", "Uzat"),
|
||||
("Scrollbar", "Kaydırma çubuğu"),
|
||||
("ScrollAuto", "Otomatik Kaydır"),
|
||||
("Good image quality", "İyi görüntü kalitesi"),
|
||||
("Balanced", "Dengelenmiş"),
|
||||
("Optimize reaction time", "Tepki süresini optimize et"),
|
||||
@@ -315,5 +317,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to restart", "Yeniden başlatmak istediğinize emin misin?"),
|
||||
("Restarting Remote Device", "Uzaktan yeniden başlatılıyor"),
|
||||
("remote_restarting_tip", ""),
|
||||
("Copied", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "原始"),
|
||||
("Shrink", "縮減"),
|
||||
("Stretch", "延展"),
|
||||
("Scrollbar", "滾動條"),
|
||||
("ScrollAuto", "自動滾動"),
|
||||
("Good image quality", "畫面品質良好"),
|
||||
("Balanced", "平衡"),
|
||||
("Optimize reaction time", "回應速度最佳化"),
|
||||
@@ -302,5 +304,6 @@ 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", "已複製"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Gốc"),
|
||||
("Shrink", "Thu nhỏ"),
|
||||
("Stretch", "Kéo dãn"),
|
||||
("Scrollbar", "Thanh cuộn"),
|
||||
("ScrollAuto", "Tự động cuộn"),
|
||||
("Good image quality", "Chất lượng hình ảnh tốt"),
|
||||
("Balanced", "Cân bằng"),
|
||||
("Optimize reaction time", "Thời gian phản ứng tối ưu"),
|
||||
@@ -302,5 +304,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to restart", "Bạn có chắc bạn muốn khởi động lại không"),
|
||||
("Restarting Remote Device", "Đang khởi động lại thiết bị từ xa"),
|
||||
("remote_restarting_tip", "Thiết bị từ xa đang khởi động lại, hãy đóng cửa sổ tin nhắn này và kết nối lại với mật khẩu vĩnh viễn sau một khoảng thời gian"),
|
||||
("Copied", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
17
src/lib.rs
17
src/lib.rs
@@ -23,13 +23,18 @@ pub mod ipc;
|
||||
pub mod ui;
|
||||
mod version;
|
||||
pub use version::*;
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
mod bridge_generated;
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub mod mobile;
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub mod mobile_ffi;
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
pub mod flutter;
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
pub mod flutter_ffi;
|
||||
use common::*;
|
||||
#[cfg(all(
|
||||
not(any(target_os = "android", target_os = "ios")),
|
||||
feature = "flutter"
|
||||
))]
|
||||
pub mod core_main;
|
||||
#[cfg(feature = "cli")]
|
||||
pub mod cli;
|
||||
#[cfg(all(windows, feature = "hbbs"))]
|
||||
@@ -42,6 +47,8 @@ mod port_forward;
|
||||
#[cfg(windows)]
|
||||
mod tray;
|
||||
|
||||
mod ui_interface;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod clipboard_file;
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ fn main() {
|
||||
.about("RustDesk command line tool")
|
||||
.args_from_usage(&args)
|
||||
.get_matches();
|
||||
use hbb_common::env_logger::*;
|
||||
use hbb_common::{env_logger::*, config::LocalConfig};
|
||||
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info"));
|
||||
if let Some(p) = matches.value_of("port-forward") {
|
||||
let options: Vec<String> = p.split(":").map(|x| x.to_owned()).collect();
|
||||
@@ -232,6 +232,7 @@ fn main() {
|
||||
remote_host = options[3].clone();
|
||||
}
|
||||
let key = matches.value_of("key").unwrap_or("").to_owned();
|
||||
cli::start_one_port_forward(options[0].clone(), port, remote_host, remote_port, key);
|
||||
let token = LocalConfig::get_option("access_token");
|
||||
cli::start_one_port_forward(options[0].clone(), port, remote_host, remote_port, key, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,579 +0,0 @@
|
||||
use crate::client::file_trait::FileManager;
|
||||
use crate::common::make_fd_to_json;
|
||||
use crate::mobile::connection_manager::{self, get_clients_length, get_clients_state};
|
||||
use crate::mobile::{self, Session};
|
||||
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
|
||||
use hbb_common::{
|
||||
config::{self, Config, LocalConfig, PeerConfig, ONLINE},
|
||||
fs, log, password_security as password, ResultType,
|
||||
};
|
||||
use serde_json::{Number, Value};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{CStr, CString},
|
||||
os::raw::c_char,
|
||||
};
|
||||
|
||||
fn initialize(app_dir: &str) {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
android_logger::init_once(
|
||||
android_logger::Config::default()
|
||||
.with_min_level(log::Level::Debug) // limit log level
|
||||
.with_tag("ffi"), // logs will show under mytag tag
|
||||
);
|
||||
}
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
use hbb_common::env_logger::*;
|
||||
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "debug"));
|
||||
}
|
||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||
crate::common::test_rendezvous_server();
|
||||
crate::common::test_nat_type();
|
||||
#[cfg(target_os = "android")]
|
||||
crate::common::check_software_update();
|
||||
}
|
||||
|
||||
pub fn start_event_stream(s: StreamSink<String>) -> ResultType<()> {
|
||||
let _ = mobile::EVENT_STREAM.write().unwrap().insert(s);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_rgba_stream(s: StreamSink<ZeroCopyBuffer<Vec<u8>>>) -> ResultType<()> {
|
||||
let _ = mobile::RGBA_STREAM.write().unwrap().insert(s);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *const c_char {
|
||||
let mut res = "".to_owned();
|
||||
let arg: &CStr = CStr::from_ptr(arg);
|
||||
let name: &CStr = CStr::from_ptr(name);
|
||||
if let Ok(name) = name.to_str() {
|
||||
match name {
|
||||
"peers" => {
|
||||
if !config::APP_DIR.read().unwrap().is_empty() {
|
||||
let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers()
|
||||
.drain(..)
|
||||
.map(|(id, _, p)| (id, p.info))
|
||||
.collect();
|
||||
res = serde_json::ser::to_string(&peers).unwrap_or("".to_owned());
|
||||
}
|
||||
}
|
||||
"remote_id" => {
|
||||
if !config::APP_DIR.read().unwrap().is_empty() {
|
||||
res = LocalConfig::get_remote_id();
|
||||
}
|
||||
}
|
||||
"remember" => {
|
||||
res = Session::get_remember().to_string();
|
||||
}
|
||||
"event" => {
|
||||
if let Some(e) = Session::pop_event() {
|
||||
res = e;
|
||||
}
|
||||
}
|
||||
"toggle_option" => {
|
||||
if let Ok(arg) = arg.to_str() {
|
||||
if let Some(v) = Session::get_toggle_option(arg) {
|
||||
res = v.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
"test_if_valid_server" => {
|
||||
if let Ok(arg) = arg.to_str() {
|
||||
res = hbb_common::socket_client::test_if_valid_server(arg);
|
||||
}
|
||||
}
|
||||
"option" => {
|
||||
if let Ok(arg) = arg.to_str() {
|
||||
res = Config::get_option(arg);
|
||||
}
|
||||
}
|
||||
"image_quality" => {
|
||||
res = Session::get_image_quality();
|
||||
}
|
||||
"software_update_url" => {
|
||||
res = crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone()
|
||||
}
|
||||
"translate" => {
|
||||
if let Ok(arg) = arg.to_str() {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(arg) {
|
||||
if let Some(locale) = m.get("locale") {
|
||||
if let Some(text) = m.get("text") {
|
||||
res = crate::client::translate_locale(text.to_owned(), locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"peer_option" => {
|
||||
if let Ok(arg) = arg.to_str() {
|
||||
res = Session::get_option(arg);
|
||||
}
|
||||
}
|
||||
"local_option" => {
|
||||
if let Ok(arg) = arg.to_str() {
|
||||
res = LocalConfig::get_option(arg);
|
||||
}
|
||||
}
|
||||
"langs" => {
|
||||
res = crate::lang::LANGS.to_string();
|
||||
}
|
||||
// File Action
|
||||
"get_home_dir" => {
|
||||
res = fs::get_home_as_string();
|
||||
}
|
||||
"read_local_dir_sync" => {
|
||||
if let Ok(value) = arg.to_str() {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let (Some(path), Some(show_hidden)) =
|
||||
(m.get("path"), m.get("show_hidden"))
|
||||
{
|
||||
if let Ok(fd) =
|
||||
fs::read_dir(&fs::get_path(path), show_hidden.eq("true"))
|
||||
{
|
||||
res = make_fd_to_json(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Server Side
|
||||
"server_id" => {
|
||||
res = Config::get_id();
|
||||
}
|
||||
"permanent_password" => {
|
||||
res = Config::get_permanent_password();
|
||||
}
|
||||
"temporary_password" => {
|
||||
res = password::temporary_password();
|
||||
}
|
||||
"connect_statue" => {
|
||||
res = ONLINE
|
||||
.lock()
|
||||
.unwrap()
|
||||
.values()
|
||||
.max()
|
||||
.unwrap_or(&0)
|
||||
.clone()
|
||||
.to_string();
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
"clients_state" => {
|
||||
res = get_clients_state();
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
"check_clients_length" => {
|
||||
if let Ok(value) = arg.to_str() {
|
||||
if value.parse::<usize>().unwrap_or(usize::MAX) != get_clients_length() {
|
||||
res = get_clients_state()
|
||||
}
|
||||
}
|
||||
}
|
||||
"uuid" => {
|
||||
res = base64::encode(hbb_common::get_uuid());
|
||||
}
|
||||
_ => {
|
||||
log::error!("Unknown name of get_by_name: {}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
CString::from_vec_unchecked(res.into_bytes()).into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) {
|
||||
let value: &CStr = CStr::from_ptr(value);
|
||||
if let Ok(value) = value.to_str() {
|
||||
let name: &CStr = CStr::from_ptr(name);
|
||||
if let Ok(name) = name.to_str() {
|
||||
match name {
|
||||
"init" => {
|
||||
initialize(value);
|
||||
}
|
||||
"info1" => {
|
||||
*crate::common::MOBILE_INFO1.lock().unwrap() = value.to_owned();
|
||||
}
|
||||
"info2" => {
|
||||
*crate::common::MOBILE_INFO2.lock().unwrap() = value.to_owned();
|
||||
}
|
||||
"connect" => {
|
||||
Session::start(value, false);
|
||||
}
|
||||
"connect_file_transfer" => {
|
||||
Session::start(value, true);
|
||||
}
|
||||
"login" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let Some(password) = m.get("password") {
|
||||
if let Some(remember) = m.get("remember") {
|
||||
Session::login(password, remember == "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"close" => {
|
||||
Session::close();
|
||||
}
|
||||
"refresh" => {
|
||||
Session::refresh();
|
||||
}
|
||||
"reconnect" => {
|
||||
Session::reconnect();
|
||||
}
|
||||
"toggle_option" => {
|
||||
Session::toggle_option(value);
|
||||
}
|
||||
"image_quality" => {
|
||||
Session::set_image_quality(value);
|
||||
}
|
||||
"lock_screen" => {
|
||||
Session::lock_screen();
|
||||
}
|
||||
"ctrl_alt_del" => {
|
||||
Session::ctrl_alt_del();
|
||||
}
|
||||
"switch_display" => {
|
||||
if let Ok(v) = value.parse::<i32>() {
|
||||
Session::switch_display(v);
|
||||
}
|
||||
}
|
||||
"remove" => {
|
||||
PeerConfig::remove(value);
|
||||
}
|
||||
"input_key" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
let alt = m.get("alt").is_some();
|
||||
let ctrl = m.get("ctrl").is_some();
|
||||
let shift = m.get("shift").is_some();
|
||||
let command = m.get("command").is_some();
|
||||
let down = m.get("down").is_some();
|
||||
let press = m.get("press").is_some();
|
||||
if let Some(name) = m.get("name") {
|
||||
Session::input_key(name, down, press, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
"input_string" => {
|
||||
Session::input_string(value);
|
||||
}
|
||||
"chat_client_mode" => {
|
||||
Session::send_chat(value.to_owned());
|
||||
}
|
||||
"send_mouse" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
let alt = m.get("alt").is_some();
|
||||
let ctrl = m.get("ctrl").is_some();
|
||||
let shift = m.get("shift").is_some();
|
||||
let command = m.get("command").is_some();
|
||||
let x = m
|
||||
.get("x")
|
||||
.map(|x| x.parse::<i32>().unwrap_or(0))
|
||||
.unwrap_or(0);
|
||||
let y = m
|
||||
.get("y")
|
||||
.map(|x| x.parse::<i32>().unwrap_or(0))
|
||||
.unwrap_or(0);
|
||||
let mut mask = 0;
|
||||
if let Some(_type) = m.get("type") {
|
||||
mask = match _type.as_str() {
|
||||
"down" => 1,
|
||||
"up" => 2,
|
||||
"wheel" => 3,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
if let Some(buttons) = m.get("buttons") {
|
||||
mask |= match buttons.as_str() {
|
||||
"left" => 1,
|
||||
"right" => 2,
|
||||
"wheel" => 4,
|
||||
_ => 0,
|
||||
} << 3;
|
||||
}
|
||||
Session::send_mouse(mask, x, y, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
"option" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let Some(name) = m.get("name") {
|
||||
if let Some(value) = m.get("value") {
|
||||
Config::set_option(name.to_owned(), value.to_owned());
|
||||
if name == "custom-rendezvous-server" {
|
||||
#[cfg(target_os = "android")]
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
crate::common::test_rendezvous_server();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"peer_option" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let Some(name) = m.get("name") {
|
||||
if let Some(value) = m.get("value") {
|
||||
Session::set_option(name.to_owned(), value.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"local_option" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let Some(name) = m.get("name") {
|
||||
if let Some(value) = m.get("value") {
|
||||
LocalConfig::set_option(name.to_owned(), value.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"input_os_password" => {
|
||||
Session::input_os_password(value.to_owned(), true);
|
||||
}
|
||||
"restart_remote_device" => {
|
||||
Session::restart_remote_device();
|
||||
}
|
||||
// File Action
|
||||
"read_remote_dir" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let (Some(path), Some(show_hidden), Some(session)) = (
|
||||
m.get("path"),
|
||||
m.get("show_hidden"),
|
||||
Session::get().read().unwrap().as_ref(),
|
||||
) {
|
||||
session.read_remote_dir(path.to_owned(), show_hidden.eq("true"));
|
||||
}
|
||||
}
|
||||
}
|
||||
"send_files" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let (
|
||||
Some(id),
|
||||
Some(path),
|
||||
Some(to),
|
||||
Some(file_num),
|
||||
Some(show_hidden),
|
||||
Some(is_remote),
|
||||
) = (
|
||||
m.get("id"),
|
||||
m.get("path"),
|
||||
m.get("to"),
|
||||
m.get("file_num"),
|
||||
m.get("show_hidden"),
|
||||
m.get("is_remote"),
|
||||
) {
|
||||
Session::send_files(
|
||||
id.parse().unwrap_or(0),
|
||||
path.to_owned(),
|
||||
to.to_owned(),
|
||||
file_num.parse().unwrap_or(0),
|
||||
show_hidden.eq("true"),
|
||||
is_remote.eq("true"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"set_confirm_override_file" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let (
|
||||
Some(id),
|
||||
Some(file_num),
|
||||
Some(need_override),
|
||||
Some(remember),
|
||||
Some(is_upload),
|
||||
) = (
|
||||
m.get("id"),
|
||||
m.get("file_num"),
|
||||
m.get("need_override"),
|
||||
m.get("remember"),
|
||||
m.get("is_upload"),
|
||||
) {
|
||||
Session::set_confirm_override_file(
|
||||
id.parse().unwrap_or(0),
|
||||
file_num.parse().unwrap_or(0),
|
||||
need_override.eq("true"),
|
||||
remember.eq("true"),
|
||||
is_upload.eq("true"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"remove_file" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let (
|
||||
Some(id),
|
||||
Some(path),
|
||||
Some(file_num),
|
||||
Some(is_remote),
|
||||
Some(session),
|
||||
) = (
|
||||
m.get("id"),
|
||||
m.get("path"),
|
||||
m.get("file_num"),
|
||||
m.get("is_remote"),
|
||||
Session::get().write().unwrap().as_mut(),
|
||||
) {
|
||||
session.remove_file(
|
||||
id.parse().unwrap_or(0),
|
||||
path.to_owned(),
|
||||
file_num.parse().unwrap_or(0),
|
||||
is_remote.eq("true"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"read_dir_recursive" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let (Some(id), Some(path), Some(is_remote), Some(session)) = (
|
||||
m.get("id"),
|
||||
m.get("path"),
|
||||
m.get("is_remote"),
|
||||
Session::get().write().unwrap().as_mut(),
|
||||
) {
|
||||
session.remove_dir_all(
|
||||
id.parse().unwrap_or(0),
|
||||
path.to_owned(),
|
||||
is_remote.eq("true"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"remove_all_empty_dirs" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let (Some(id), Some(path), Some(is_remote), Some(session)) = (
|
||||
m.get("id"),
|
||||
m.get("path"),
|
||||
m.get("is_remote"),
|
||||
Session::get().write().unwrap().as_mut(),
|
||||
) {
|
||||
session.remove_dir(
|
||||
id.parse().unwrap_or(0),
|
||||
path.to_owned(),
|
||||
is_remote.eq("true"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"cancel_job" => {
|
||||
if let (Ok(id), Some(session)) =
|
||||
(value.parse(), Session::get().write().unwrap().as_mut())
|
||||
{
|
||||
session.cancel_job(id);
|
||||
}
|
||||
}
|
||||
"create_dir" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
|
||||
if let (Some(id), Some(path), Some(is_remote), Some(session)) = (
|
||||
m.get("id"),
|
||||
m.get("path"),
|
||||
m.get("is_remote"),
|
||||
Session::get().write().unwrap().as_mut(),
|
||||
) {
|
||||
session.create_dir(
|
||||
id.parse().unwrap_or(0),
|
||||
path.to_owned(),
|
||||
is_remote.eq("true"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Server Side
|
||||
"permanent_password" => Config::set_permanent_password(value),
|
||||
"temporary_password" => {
|
||||
password::update_temporary_password();
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
"chat_server_mode" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, Value>>(value) {
|
||||
if let (Some(Value::Number(id)), Some(Value::String(text))) =
|
||||
(m.get("id"), m.get("text"))
|
||||
{
|
||||
let id = id.as_i64().unwrap_or(0);
|
||||
connection_manager::send_chat(id as i32, text.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
"home_dir" => {
|
||||
*config::APP_HOME_DIR.write().unwrap() = value.to_owned();
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
"login_res" => {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, Value>>(value) {
|
||||
if let (Some(Value::Number(id)), Some(Value::Bool(res))) =
|
||||
(m.get("id"), m.get("res"))
|
||||
{
|
||||
let id = id.as_i64().unwrap_or(0);
|
||||
connection_manager::on_login_res(id as i32, *res);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
"stop_service" => {
|
||||
Config::set_option("stop-service".into(), "Y".into());
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
"start_service" => {
|
||||
Config::set_option("stop-service".into(), "".into());
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
"close_conn" => {
|
||||
if let Ok(id) = value.parse::<i32>() {
|
||||
connection_manager::close_conn(id);
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
log::error!("Unknown name of set_by_name: {}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use hbb_common::{config::Config, log};
|
||||
use jni::{
|
||||
objects::{JClass, JString},
|
||||
sys::jstring,
|
||||
JNIEnv,
|
||||
};
|
||||
|
||||
use crate::start_server;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
) {
|
||||
log::debug!("startServer from java");
|
||||
std::thread::spawn(move || start_server(true));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
locale: JString,
|
||||
input: JString,
|
||||
) -> jstring {
|
||||
let res = if let (Ok(input), Ok(locale)) = (env.get_string(input), env.get_string(locale)) {
|
||||
let input: String = input.into();
|
||||
let locale: String = locale.into();
|
||||
crate::client::translate_locale(input, &locale)
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
return env.new_string(res).unwrap_or(input).into_inner();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_refreshScreen(
|
||||
_env: JNIEnv,
|
||||
_class: JClass,
|
||||
) {
|
||||
crate::server::video_service::refresh()
|
||||
}
|
||||
}
|
||||
@@ -629,3 +629,9 @@ extern "C" {
|
||||
pub fn quit_gui() {
|
||||
unsafe { gtk_main_quit() };
|
||||
}
|
||||
|
||||
pub fn check_super_user_permission() -> ResultType<bool> {
|
||||
// TODO: replace echo with a rustdesk's program, which is location-fixed and non-gui.
|
||||
let status = std::process::Command::new("pkexec").arg("echo").status()?;
|
||||
Ok(status.success() && status.code() == Some(0))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use hbb_common::{
|
||||
};
|
||||
use std::io::prelude::*;
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
ffi::{CString, OsString},
|
||||
fs, io, mem,
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
@@ -17,7 +17,8 @@ use winapi::{
|
||||
shared::{minwindef::*, ntdef::NULL, windef::*},
|
||||
um::{
|
||||
errhandlingapi::GetLastError, handleapi::CloseHandle, minwinbase::STILL_ACTIVE,
|
||||
processthreadsapi::GetExitCodeProcess, winbase::*, wingdi::*, winnt::HANDLE, winuser::*,
|
||||
processthreadsapi::GetExitCodeProcess, shellapi::ShellExecuteA, winbase::*, wingdi::*,
|
||||
winnt::HANDLE, winuser::*,
|
||||
},
|
||||
};
|
||||
use windows_service::{
|
||||
@@ -1418,3 +1419,17 @@ pub fn get_user_token(session_id: u32, as_user: bool) -> HANDLE {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_super_user_permission() -> ResultType<bool> {
|
||||
unsafe {
|
||||
let ret = ShellExecuteA(
|
||||
NULL as _,
|
||||
CString::new("runas")?.as_ptr() as _,
|
||||
CString::new("cmd")?.as_ptr() as _,
|
||||
CString::new("/c /q")?.as_ptr() as _,
|
||||
NULL as _,
|
||||
SW_SHOWNORMAL,
|
||||
);
|
||||
return Ok(ret as i32 > 32);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::client::*;
|
||||
use hbb_common::{
|
||||
allow_err, bail,
|
||||
config::CONNECT_TIMEOUT,
|
||||
config::READ_TIMEOUT,
|
||||
futures::{SinkExt, StreamExt},
|
||||
log,
|
||||
message_proto::*,
|
||||
@@ -105,22 +105,61 @@ async fn connect_and_login(
|
||||
key: &str,
|
||||
token: &str,
|
||||
is_rdp: bool,
|
||||
) -> ResultType<Option<Stream>> {
|
||||
let mut res = connect_and_login_2(
|
||||
id,
|
||||
password,
|
||||
ui_receiver,
|
||||
interface.clone(),
|
||||
forward,
|
||||
key,
|
||||
token,
|
||||
is_rdp,
|
||||
)
|
||||
.await;
|
||||
if res.is_err() && interface.is_force_relay() {
|
||||
res = connect_and_login_2(
|
||||
id,
|
||||
password,
|
||||
ui_receiver,
|
||||
interface,
|
||||
forward,
|
||||
key,
|
||||
token,
|
||||
is_rdp,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
async fn connect_and_login_2(
|
||||
id: &str,
|
||||
password: &str,
|
||||
ui_receiver: &mut mpsc::UnboundedReceiver<Data>,
|
||||
interface: impl Interface,
|
||||
forward: &mut Framed<TcpStream, BytesCodec>,
|
||||
key: &str,
|
||||
token: &str,
|
||||
is_rdp: bool,
|
||||
) -> ResultType<Option<Stream>> {
|
||||
let conn_type = if is_rdp {
|
||||
ConnType::RDP
|
||||
} else {
|
||||
ConnType::PORT_FORWARD
|
||||
};
|
||||
let (mut stream, _) = Client::start(id, key, token, conn_type).await?;
|
||||
let (mut stream, direct) = Client::start(id, key, token, conn_type, interface.clone()).await?;
|
||||
let mut interface = interface;
|
||||
let mut buffer = Vec::new();
|
||||
let mut received = false;
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = timeout(CONNECT_TIMEOUT, stream.next()) => match res {
|
||||
res = timeout(READ_TIMEOUT, stream.next()) => match res {
|
||||
Err(_) => {
|
||||
bail!("Timeout");
|
||||
}
|
||||
Ok(Some(Ok(bytes))) => {
|
||||
received = true;
|
||||
let msg_in = Message::parse_from_bytes(&bytes)?;
|
||||
match msg_in.union {
|
||||
Some(message::Union::Hash(hash)) => {
|
||||
@@ -143,6 +182,11 @@ async fn connect_and_login(
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(Some(Err(err))) => {
|
||||
log::error!("Connection closed: {}", err);
|
||||
interface.set_force_relay(direct, received);
|
||||
bail!("Connection closed: {}", err);
|
||||
}
|
||||
_ => {
|
||||
bail!("Reset by the peer");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
use crate::server::{check_zombie, new as new_server, ServerPtr};
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use hbb_common::config::DiscoveryPeer;
|
||||
use hbb_common::tcp::FramedStream;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::bail,
|
||||
config,
|
||||
config::{Config, REG_INTERVAL, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
|
||||
futures::future::join_all,
|
||||
log,
|
||||
@@ -15,15 +29,8 @@ use hbb_common::{
|
||||
udp::FramedSocket,
|
||||
AddrMangle, IntoTargetAddr, ResultType, TargetAddr,
|
||||
};
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::server::{check_zombie, new as new_server, ServerPtr};
|
||||
|
||||
type Message = RendezvousMessage;
|
||||
|
||||
@@ -353,7 +360,14 @@ impl RendezvousMediator {
|
||||
{
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
return self
|
||||
.create_relay(ph.socket_addr.into(), relay_server, uuid, server, true, true)
|
||||
.create_relay(
|
||||
ph.socket_addr.into(),
|
||||
relay_server,
|
||||
uuid,
|
||||
server,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let peer_addr = AddrMangle::decode(&ph.socket_addr);
|
||||
@@ -540,3 +554,188 @@ async fn direct_server(server: ServerPtr) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_broadcast_port() -> u16 {
|
||||
(RENDEZVOUS_PORT + 3) as _
|
||||
}
|
||||
|
||||
pub fn get_mac() -> String {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Ok(Some(mac)) = mac_address::get_mac_address() {
|
||||
mac.to_string()
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
"".to_owned()
|
||||
}
|
||||
|
||||
fn lan_discovery() -> ResultType<()> {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], get_broadcast_port()));
|
||||
let socket = std::net::UdpSocket::bind(addr)?;
|
||||
socket.set_read_timeout(Some(std::time::Duration::from_millis(1000)))?;
|
||||
log::info!("lan discovery listener started");
|
||||
loop {
|
||||
let mut buf = [0; 2048];
|
||||
if let Ok((len, addr)) = socket.recv_from(&mut buf) {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&buf[0..len]) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::PeerDiscovery(p)) => {
|
||||
if p.cmd == "ping" {
|
||||
let mut msg_out = Message::new();
|
||||
let peer = PeerDiscovery {
|
||||
cmd: "pong".to_owned(),
|
||||
mac: get_mac(),
|
||||
id: Config::get_id(),
|
||||
hostname: whoami::hostname(),
|
||||
username: crate::platform::get_active_username(),
|
||||
platform: whoami::platform().to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
msg_out.set_peer_discovery(peer);
|
||||
socket.send_to(&msg_out.write_to_bytes()?, addr).ok();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn query_online_states<F: FnOnce(Vec<String>, Vec<String>)>(ids: Vec<String>, f: F) {
|
||||
let test = false;
|
||||
if test {
|
||||
sleep(1.5).await;
|
||||
let mut onlines = ids;
|
||||
let offlines = onlines.drain((onlines.len() / 2)..).collect();
|
||||
f(onlines, offlines)
|
||||
} else {
|
||||
let query_begin = Instant::now();
|
||||
let query_timeout = std::time::Duration::from_millis(3_000);
|
||||
loop {
|
||||
if SHOULD_EXIT.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
match query_online_states_(&ids, query_timeout).await {
|
||||
Ok((onlines, offlines)) => {
|
||||
f(onlines, offlines);
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("{}", &e);
|
||||
}
|
||||
}
|
||||
|
||||
if query_begin.elapsed() > query_timeout {
|
||||
log::debug!("query onlines timeout {:?}", query_timeout);
|
||||
break;
|
||||
}
|
||||
|
||||
sleep(1.5).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_online_stream() -> ResultType<FramedStream> {
|
||||
let (mut rendezvous_server, servers, contained) = crate::get_rendezvous_server(1_000).await;
|
||||
let tmp: Vec<&str> = rendezvous_server.split(":").collect();
|
||||
if tmp.len() != 2 {
|
||||
bail!("Invalid server address: {}", rendezvous_server);
|
||||
}
|
||||
let port: u16 = tmp[1].parse()?;
|
||||
if port == 0 {
|
||||
bail!("Invalid server address: {}", rendezvous_server);
|
||||
}
|
||||
let online_server = format!("{}:{}", tmp[0], port - 1);
|
||||
let server_addr = socket_client::get_target_addr(&online_server)?;
|
||||
socket_client::connect_tcp(
|
||||
server_addr,
|
||||
Config::get_any_listen_addr(),
|
||||
RENDEZVOUS_TIMEOUT,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn query_online_states_(
|
||||
ids: &Vec<String>,
|
||||
timeout: std::time::Duration,
|
||||
) -> ResultType<(Vec<String>, Vec<String>)> {
|
||||
let query_begin = Instant::now();
|
||||
|
||||
let mut msg_out = RendezvousMessage::new();
|
||||
msg_out.set_online_request(OnlineRequest {
|
||||
id: Config::get_id(),
|
||||
peers: ids.clone(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
loop {
|
||||
if SHOULD_EXIT.load(Ordering::SeqCst) {
|
||||
// No need to care about onlines
|
||||
return Ok((Vec::new(), Vec::new()));
|
||||
}
|
||||
|
||||
let mut socket = create_online_stream().await?;
|
||||
socket.send(&msg_out).await?;
|
||||
match socket.next_timeout(RENDEZVOUS_TIMEOUT).await {
|
||||
Some(Ok(bytes)) => {
|
||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::OnlineResponse(online_response)) => {
|
||||
let states = online_response.states;
|
||||
let mut onlines = Vec::new();
|
||||
let mut offlines = Vec::new();
|
||||
for i in 0..ids.len() {
|
||||
// bytes index from left to right
|
||||
let bit_value = 0x01 << (7 - i % 8);
|
||||
if (states[i / 8] & bit_value) == bit_value {
|
||||
onlines.push(ids[i].clone());
|
||||
} else {
|
||||
offlines.push(ids[i].clone());
|
||||
}
|
||||
}
|
||||
return Ok((onlines, offlines));
|
||||
}
|
||||
_ => {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
log::error!("Failed to receive {e}");
|
||||
}
|
||||
None => {
|
||||
// TODO: Make sure socket closed?
|
||||
bail!("Online stream receives None");
|
||||
}
|
||||
}
|
||||
|
||||
if query_begin.elapsed() > timeout {
|
||||
bail!("Try query onlines timeout {:?}", &timeout);
|
||||
}
|
||||
|
||||
sleep(300.0).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_query_onlines() {
|
||||
super::query_online_states(
|
||||
vec![
|
||||
"152183996".to_owned(),
|
||||
"165782066".to_owned(),
|
||||
"155323351".to_owned(),
|
||||
"460952777".to_owned(),
|
||||
],
|
||||
|onlines: Vec<String>, offlines: Vec<String>| {
|
||||
println!("onlines: {:?}, offlines: {:?}", &onlines, &offlines);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,12 +307,26 @@ pub fn check_zombie() {
|
||||
});
|
||||
}
|
||||
|
||||
/// Start the host server that allows the remote peer to control the current machine.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `is_server` - Whether the current client is definitely the server.
|
||||
/// If true, the server will be started.
|
||||
/// 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) {
|
||||
crate::RendezvousMediator::start_all().await;
|
||||
}
|
||||
|
||||
/// Start the host server that allows the remote peer to control the current machine.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `is_server` - Whether the current client is definitely the server.
|
||||
/// If true, the server will be started.
|
||||
/// Otherwise, client will check if there's already a server and start one if not.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[tokio::main]
|
||||
pub async fn start_server(is_server: bool) {
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::clipboard_file::*;
|
||||
use crate::common::update_clipboard;
|
||||
use crate::video_service;
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
use crate::{common::MOBILE_INFO2, mobile::connection_manager::start_channel};
|
||||
use crate::{ipc};
|
||||
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
|
||||
use crate::{ipc, VERSION};
|
||||
use hbb_common::{
|
||||
config::Config,
|
||||
fs,
|
||||
@@ -644,7 +644,7 @@ impl Connection {
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
pi.hostname = MOBILE_INFO2.lock().unwrap().clone();
|
||||
pi.hostname = DEVICE_NAME.lock().unwrap().clone();
|
||||
pi.platform = "Android".into();
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
|
||||
478
src/ui.rs
478
src/ui.rs
@@ -1,13 +1,12 @@
|
||||
mod cm;
|
||||
#[cfg(feature = "inline")]
|
||||
mod inline;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
pub mod remote;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod win_privacy;
|
||||
use crate::common::SOFTWARE_UPDATE_URL;
|
||||
use crate::ipc;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
iter::FromIterator,
|
||||
process::Child,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use sciter::Value;
|
||||
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
|
||||
@@ -19,14 +18,35 @@ use hbb_common::{
|
||||
tcp::FramedStream,
|
||||
tokio::{self, sync::mpsc, time},
|
||||
};
|
||||
use sciter::Value;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
iter::FromIterator,
|
||||
process::Child,
|
||||
sync::{Arc, Mutex},
|
||||
|
||||
use crate::common::{get_app_name, SOFTWARE_UPDATE_URL};
|
||||
use crate::ipc;
|
||||
use crate::ui_interface::{
|
||||
check_mouse_time, closing, create_shortcut, current_is_wayland, fix_login_wayland,
|
||||
forget_password, get_api_server, get_async_job_status, get_connect_status, get_error, get_fav,
|
||||
get_icon, get_lan_peers, get_langs, get_license, get_local_option, get_mouse_time,
|
||||
get_new_version, get_option, get_options, get_peer, get_peer_option, get_recent_sessions,
|
||||
get_remote_id, get_size, get_socks, get_software_ext, get_software_store_path,
|
||||
get_software_update_url, get_uuid, get_version, goto_install, has_hwcodec,
|
||||
has_rendezvous_service, install_me, install_path, is_can_screen_recording, is_installed,
|
||||
is_installed_daemon, is_installed_lower_version, is_login_wayland, is_ok_change_id,
|
||||
is_process_trusted, is_rdp_service_open, is_share_rdp, is_xfce, modify_default_login,
|
||||
new_remote, open_url, peer_has_password, permanent_password, post_request,
|
||||
recent_sessions_updated, remove_peer, run_without_install, set_local_option, set_option,
|
||||
set_options, set_peer_option, set_permanent_password, set_remote_id, set_share_rdp, set_socks,
|
||||
show_run_without_install, store_fav, t, temporary_password, test_if_valid_server, update_me,
|
||||
update_temporary_password, using_public_server,
|
||||
};
|
||||
|
||||
mod cm;
|
||||
#[cfg(feature = "inline")]
|
||||
mod inline;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
pub mod remote;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod win_privacy;
|
||||
|
||||
type Message = RendezvousMessage;
|
||||
|
||||
pub type Childs = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
|
||||
@@ -37,15 +57,6 @@ lazy_static::lazy_static! {
|
||||
static ref STUPID_VALUES: Mutex<Vec<Arc<Vec<Value>>>> = Default::default();
|
||||
}
|
||||
|
||||
struct UI(
|
||||
Childs,
|
||||
Arc<Mutex<Status>>,
|
||||
Arc<Mutex<HashMap<String, String>>>,
|
||||
Arc<Mutex<String>>,
|
||||
mpsc::UnboundedSender<ipc::Data>,
|
||||
Arc<Mutex<String>>,
|
||||
);
|
||||
|
||||
struct UIHostHandler;
|
||||
|
||||
pub fn start(args: &mut [String]) {
|
||||
@@ -102,16 +113,14 @@ pub fn start(args: &mut [String]) {
|
||||
args[1] = id;
|
||||
}
|
||||
if args.is_empty() {
|
||||
let childs: Childs = Default::default();
|
||||
let cloned = childs.clone();
|
||||
std::thread::spawn(move || check_zombie(cloned));
|
||||
let child: Childs = Default::default();
|
||||
std::thread::spawn(move || check_zombie(child));
|
||||
crate::common::check_software_update();
|
||||
frame.event_handler(UI::new(childs));
|
||||
frame.event_handler(UI {});
|
||||
frame.sciter_handler(UIHostHandler {});
|
||||
page = "index.html";
|
||||
} else if args[0] == "--install" {
|
||||
let childs: Childs = Default::default();
|
||||
frame.event_handler(UI::new(childs));
|
||||
frame.event_handler(UI {});
|
||||
frame.sciter_handler(UIHostHandler {});
|
||||
page = "install.html";
|
||||
} else if args[0] == "--cm" {
|
||||
@@ -173,20 +182,11 @@ pub fn start(args: &mut [String]) {
|
||||
frame.run_app();
|
||||
}
|
||||
|
||||
impl UI {
|
||||
fn new(childs: Childs) -> Self {
|
||||
let res = check_connect_status(true);
|
||||
Self(childs, res.0, res.1, Default::default(), res.2, res.3)
|
||||
}
|
||||
struct UI {}
|
||||
|
||||
fn recent_sessions_updated(&mut self) -> bool {
|
||||
let mut lock = self.0.lock().unwrap();
|
||||
if lock.0 {
|
||||
lock.0 = false;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
impl UI {
|
||||
fn recent_sessions_updated(&self) -> bool {
|
||||
recent_sessions_updated()
|
||||
}
|
||||
|
||||
fn get_id(&self) -> String {
|
||||
@@ -194,182 +194,104 @@ impl UI {
|
||||
}
|
||||
|
||||
fn temporary_password(&mut self) -> String {
|
||||
self.5.lock().unwrap().clone()
|
||||
temporary_password()
|
||||
}
|
||||
|
||||
fn update_temporary_password(&self) {
|
||||
allow_err!(ipc::update_temporary_password());
|
||||
update_temporary_password()
|
||||
}
|
||||
|
||||
fn permanent_password(&self) -> String {
|
||||
ipc::get_permanent_password()
|
||||
permanent_password()
|
||||
}
|
||||
|
||||
fn set_permanent_password(&self, password: String) {
|
||||
allow_err!(ipc::set_permanent_password(password));
|
||||
set_permanent_password(password);
|
||||
}
|
||||
|
||||
fn get_remote_id(&mut self) -> String {
|
||||
LocalConfig::get_remote_id()
|
||||
get_remote_id()
|
||||
}
|
||||
|
||||
fn set_remote_id(&mut self, id: String) {
|
||||
LocalConfig::set_remote_id(&id);
|
||||
set_remote_id(id);
|
||||
}
|
||||
|
||||
fn goto_install(&mut self) {
|
||||
allow_err!(crate::run_me(vec!["--install"]));
|
||||
goto_install();
|
||||
}
|
||||
|
||||
fn install_me(&mut self, _options: String, _path: String) {
|
||||
#[cfg(windows)]
|
||||
std::thread::spawn(move || {
|
||||
allow_err!(crate::platform::windows::install_me(
|
||||
&_options, _path, false, false
|
||||
));
|
||||
std::process::exit(0);
|
||||
});
|
||||
install_me(_options, _path, false, false);
|
||||
}
|
||||
|
||||
fn update_me(&self, _path: String) {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
std::process::Command::new("pkexec")
|
||||
.args(&["apt", "install", "-f", &_path])
|
||||
.spawn()
|
||||
.ok();
|
||||
std::fs::remove_file(&_path).ok();
|
||||
crate::run_me(Vec::<&str>::new()).ok();
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut path = _path;
|
||||
if path.is_empty() {
|
||||
if let Ok(tmp) = std::env::current_exe() {
|
||||
path = tmp.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
std::process::Command::new(path)
|
||||
.arg("--update")
|
||||
.spawn()
|
||||
.ok();
|
||||
std::process::exit(0);
|
||||
}
|
||||
update_me(_path);
|
||||
}
|
||||
|
||||
fn run_without_install(&self) {
|
||||
crate::run_me(vec!["--noinstall"]).ok();
|
||||
std::process::exit(0);
|
||||
run_without_install();
|
||||
}
|
||||
|
||||
fn show_run_without_install(&self) -> bool {
|
||||
let mut it = std::env::args();
|
||||
if let Some(tmp) = it.next() {
|
||||
if crate::is_setup(&tmp) {
|
||||
return it.next() == None;
|
||||
}
|
||||
}
|
||||
false
|
||||
show_run_without_install()
|
||||
}
|
||||
|
||||
fn has_rendezvous_service(&self) -> bool {
|
||||
#[cfg(all(windows, feature = "hbbs"))]
|
||||
return crate::platform::is_win_server()
|
||||
&& crate::platform::windows::get_license().is_some();
|
||||
return false;
|
||||
has_rendezvous_service()
|
||||
}
|
||||
|
||||
fn get_license(&self) -> String {
|
||||
#[cfg(windows)]
|
||||
if let Some(lic) = crate::platform::windows::get_license() {
|
||||
return format!(
|
||||
"<br /> Key: {} <br /> Host: {} Api: {}",
|
||||
lic.key, lic.host, lic.api
|
||||
);
|
||||
}
|
||||
Default::default()
|
||||
get_license()
|
||||
}
|
||||
|
||||
fn get_option(&self, key: String) -> String {
|
||||
self.get_option_(&key)
|
||||
}
|
||||
|
||||
fn get_option_(&self, key: &str) -> String {
|
||||
if let Some(v) = self.2.lock().unwrap().get(key) {
|
||||
v.to_owned()
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
get_option(key)
|
||||
}
|
||||
|
||||
fn get_local_option(&self, key: String) -> String {
|
||||
LocalConfig::get_option(&key)
|
||||
get_local_option(key)
|
||||
}
|
||||
|
||||
fn set_local_option(&self, key: String, value: String) {
|
||||
LocalConfig::set_option(key, value);
|
||||
set_local_option(key, value);
|
||||
}
|
||||
|
||||
fn peer_has_password(&self, id: String) -> bool {
|
||||
!PeerConfig::load(&id).password.is_empty()
|
||||
peer_has_password(id)
|
||||
}
|
||||
|
||||
fn forget_password(&self, id: String) {
|
||||
let mut c = PeerConfig::load(&id);
|
||||
c.password.clear();
|
||||
c.store(&id);
|
||||
forget_password(id)
|
||||
}
|
||||
|
||||
fn get_peer_option(&self, id: String, name: String) -> String {
|
||||
let c = PeerConfig::load(&id);
|
||||
c.options.get(&name).unwrap_or(&"".to_owned()).to_owned()
|
||||
get_peer_option(id, name)
|
||||
}
|
||||
|
||||
fn set_peer_option(&self, id: String, name: String, value: String) {
|
||||
let mut c = PeerConfig::load(&id);
|
||||
if value.is_empty() {
|
||||
c.options.remove(&name);
|
||||
} else {
|
||||
c.options.insert(name, value);
|
||||
}
|
||||
c.store(&id);
|
||||
set_peer_option(id, name, value)
|
||||
}
|
||||
|
||||
fn using_public_server(&self) -> bool {
|
||||
crate::get_custom_rendezvous_server(self.get_option_("custom-rendezvous-server")).is_empty()
|
||||
using_public_server()
|
||||
}
|
||||
|
||||
fn get_options(&self) -> Value {
|
||||
let hashmap: HashMap<String, String> = serde_json::from_str(&get_options()).unwrap();
|
||||
let mut m = Value::map();
|
||||
for (k, v) in self.2.lock().unwrap().iter() {
|
||||
for (k, v) in hashmap {
|
||||
m.set_item(k, v);
|
||||
}
|
||||
m
|
||||
}
|
||||
|
||||
fn test_if_valid_server(&self, host: String) -> String {
|
||||
hbb_common::socket_client::test_if_valid_server(&host)
|
||||
test_if_valid_server(host)
|
||||
}
|
||||
|
||||
fn get_sound_inputs(&self) -> Value {
|
||||
let mut a = Value::array(0);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let inputs = Arc::new(Mutex::new(Vec::new()));
|
||||
let cloned = inputs.clone();
|
||||
// can not call below in UI thread, because conflict with sciter sound com initialization
|
||||
std::thread::spawn(move || *cloned.lock().unwrap() = get_sound_inputs())
|
||||
.join()
|
||||
.ok();
|
||||
for name in inputs.lock().unwrap().drain(..) {
|
||||
a.push(name);
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
for name in get_sound_inputs() {
|
||||
a.push(name);
|
||||
}
|
||||
a
|
||||
Value::from_iter(get_sound_inputs())
|
||||
}
|
||||
|
||||
fn set_options(&self, v: Value) {
|
||||
@@ -383,119 +305,64 @@ impl UI {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*self.2.lock().unwrap() = m.clone();
|
||||
ipc::set_options(m).ok();
|
||||
set_options(m);
|
||||
}
|
||||
|
||||
fn set_option(&self, key: String, value: String) {
|
||||
#[cfg(target_os = "macos")]
|
||||
if &key == "stop-service" {
|
||||
let is_stop = value == "Y";
|
||||
if is_stop && crate::platform::macos::uninstall() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let mut options = self.2.lock().unwrap();
|
||||
if value.is_empty() {
|
||||
options.remove(&key);
|
||||
} else {
|
||||
options.insert(key.clone(), value.clone());
|
||||
}
|
||||
ipc::set_options(options.clone()).ok();
|
||||
set_option(key, value);
|
||||
}
|
||||
|
||||
fn install_path(&mut self) -> String {
|
||||
#[cfg(windows)]
|
||||
return crate::platform::windows::get_install_info().1;
|
||||
#[cfg(not(windows))]
|
||||
return "".to_owned();
|
||||
install_path()
|
||||
}
|
||||
|
||||
fn get_socks(&self) -> Value {
|
||||
let s = ipc::get_socks();
|
||||
match s {
|
||||
None => Value::null(),
|
||||
Some(s) => {
|
||||
let mut v = Value::array(0);
|
||||
v.push(s.proxy);
|
||||
v.push(s.username);
|
||||
v.push(s.password);
|
||||
v
|
||||
}
|
||||
}
|
||||
Value::from_iter(get_socks())
|
||||
}
|
||||
|
||||
fn set_socks(&self, proxy: String, username: String, password: String) {
|
||||
ipc::set_socks(config::Socks5Server {
|
||||
proxy,
|
||||
username,
|
||||
password,
|
||||
})
|
||||
.ok();
|
||||
set_socks(proxy, username, password)
|
||||
}
|
||||
|
||||
fn is_installed(&self) -> bool {
|
||||
crate::platform::is_installed()
|
||||
is_installed()
|
||||
}
|
||||
|
||||
fn is_rdp_service_open(&self) -> bool {
|
||||
#[cfg(windows)]
|
||||
return self.is_installed() && crate::platform::windows::is_rdp_service_open();
|
||||
#[cfg(not(windows))]
|
||||
return false;
|
||||
is_rdp_service_open()
|
||||
}
|
||||
|
||||
fn is_share_rdp(&self) -> bool {
|
||||
#[cfg(windows)]
|
||||
return crate::platform::windows::is_share_rdp();
|
||||
#[cfg(not(windows))]
|
||||
return false;
|
||||
is_share_rdp()
|
||||
}
|
||||
|
||||
fn set_share_rdp(&self, _enable: bool) {
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::set_share_rdp(_enable);
|
||||
set_share_rdp(_enable);
|
||||
}
|
||||
|
||||
fn is_installed_lower_version(&self) -> bool {
|
||||
#[cfg(not(windows))]
|
||||
return false;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let installed_version = crate::platform::windows::get_installed_version();
|
||||
let a = hbb_common::get_version_number(crate::VERSION);
|
||||
let b = hbb_common::get_version_number(&installed_version);
|
||||
return a > b;
|
||||
}
|
||||
is_installed_lower_version()
|
||||
}
|
||||
|
||||
fn closing(&mut self, x: i32, y: i32, w: i32, h: i32) {
|
||||
crate::server::input_service::fix_key_down_timeout_at_exit();
|
||||
LocalConfig::set_size(x, y, w, h);
|
||||
closing(x, y, w, h)
|
||||
}
|
||||
|
||||
fn get_size(&mut self) -> Value {
|
||||
let s = LocalConfig::get_size();
|
||||
let mut v = Value::array(0);
|
||||
v.push(s.0);
|
||||
v.push(s.1);
|
||||
v.push(s.2);
|
||||
v.push(s.3);
|
||||
v
|
||||
Value::from_iter(get_size())
|
||||
}
|
||||
|
||||
fn get_mouse_time(&self) -> f64 {
|
||||
self.1.lock().unwrap().2 as _
|
||||
get_mouse_time()
|
||||
}
|
||||
|
||||
fn check_mouse_time(&self) {
|
||||
allow_err!(self.4.send(ipc::Data::MouseMoveTime(0)));
|
||||
check_mouse_time()
|
||||
}
|
||||
|
||||
fn get_connect_status(&mut self) -> Value {
|
||||
let mut v = Value::array(0);
|
||||
let x = self.1.lock().unwrap().clone();
|
||||
let x = get_connect_status();
|
||||
v.push(x.0);
|
||||
v.push(x.1);
|
||||
v.push(x.3);
|
||||
@@ -515,12 +382,12 @@ impl UI {
|
||||
}
|
||||
|
||||
fn get_peer(&self, id: String) -> Value {
|
||||
let c = PeerConfig::load(&id);
|
||||
let c = get_peer(id.clone());
|
||||
Self::get_peer_value(id, c)
|
||||
}
|
||||
|
||||
fn get_fav(&self) -> Value {
|
||||
Value::from_iter(LocalConfig::get_fav())
|
||||
Value::from_iter(get_fav())
|
||||
}
|
||||
|
||||
fn store_fav(&self, fav: Value) {
|
||||
@@ -532,12 +399,12 @@ impl UI {
|
||||
}
|
||||
}
|
||||
});
|
||||
LocalConfig::set_fav(tmp);
|
||||
store_fav(tmp);
|
||||
}
|
||||
|
||||
fn get_recent_sessions(&mut self) -> Value {
|
||||
// to-do: limit number of recent sessions, and remove old peer file
|
||||
let peers: Vec<Value> = PeerConfig::peers()
|
||||
let peers: Vec<Value> = get_recent_sessions()
|
||||
.drain(..)
|
||||
.map(|p| Self::get_peer_value(p.0, p.2))
|
||||
.collect();
|
||||
@@ -545,11 +412,11 @@ impl UI {
|
||||
}
|
||||
|
||||
fn get_icon(&mut self) -> String {
|
||||
crate::get_icon()
|
||||
get_icon()
|
||||
}
|
||||
|
||||
fn remove_peer(&mut self, id: String) {
|
||||
PeerConfig::remove(&id);
|
||||
remove_peer(id)
|
||||
}
|
||||
|
||||
fn remove_discovered(&mut self, id: String) {
|
||||
@@ -563,145 +430,67 @@ impl UI {
|
||||
}
|
||||
|
||||
fn new_remote(&mut self, id: String, remote_type: String) {
|
||||
let mut lock = self.0.lock().unwrap();
|
||||
let args = vec![format!("--{}", remote_type), id.clone()];
|
||||
let key = (id.clone(), remote_type.clone());
|
||||
if let Some(c) = lock.1.get_mut(&key) {
|
||||
if let Ok(Some(_)) = c.try_wait() {
|
||||
lock.1.remove(&key);
|
||||
} else {
|
||||
if remote_type == "rdp" {
|
||||
allow_err!(c.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
c.try_wait().ok();
|
||||
lock.1.remove(&key);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
match crate::run_me(args) {
|
||||
Ok(child) => {
|
||||
lock.1.insert(key, child);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to spawn remote: {}", err);
|
||||
}
|
||||
}
|
||||
new_remote(id, remote_type)
|
||||
}
|
||||
|
||||
fn is_process_trusted(&mut self, _prompt: bool) -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
return crate::platform::macos::is_process_trusted(_prompt);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
return true;
|
||||
is_process_trusted(_prompt)
|
||||
}
|
||||
|
||||
fn is_can_screen_recording(&mut self, _prompt: bool) -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
return crate::platform::macos::is_can_screen_recording(_prompt);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
return true;
|
||||
is_can_screen_recording(_prompt)
|
||||
}
|
||||
|
||||
fn is_installed_daemon(&mut self, _prompt: bool) -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
return crate::platform::macos::is_installed_daemon(_prompt);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
return true;
|
||||
is_installed_daemon(_prompt)
|
||||
}
|
||||
|
||||
fn get_error(&mut self) -> String {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let dtype = crate::platform::linux::get_display_server();
|
||||
if "wayland" == dtype {
|
||||
return "".to_owned();
|
||||
}
|
||||
if dtype != "x11" {
|
||||
return format!(
|
||||
"{} {}, {}",
|
||||
self.t("Unsupported display server ".to_owned()),
|
||||
dtype,
|
||||
self.t("x11 expected".to_owned()),
|
||||
);
|
||||
}
|
||||
}
|
||||
return "".to_owned();
|
||||
get_error()
|
||||
}
|
||||
|
||||
fn is_login_wayland(&mut self) -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
return crate::platform::linux::is_login_wayland();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return false;
|
||||
is_login_wayland()
|
||||
}
|
||||
|
||||
fn fix_login_wayland(&mut self) {
|
||||
/*
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::platform::linux::fix_login_wayland();
|
||||
*/
|
||||
fix_login_wayland()
|
||||
}
|
||||
|
||||
fn current_is_wayland(&mut self) -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
return crate::platform::linux::current_is_wayland();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return false;
|
||||
current_is_wayland()
|
||||
}
|
||||
|
||||
fn modify_default_login(&mut self) -> String {
|
||||
/*
|
||||
#[cfg(target_os = "linux")]
|
||||
return crate::platform::linux::modify_default_login();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
*/
|
||||
return "".to_owned();
|
||||
modify_default_login()
|
||||
}
|
||||
|
||||
fn get_software_update_url(&self) -> String {
|
||||
SOFTWARE_UPDATE_URL.lock().unwrap().clone()
|
||||
get_software_update_url()
|
||||
}
|
||||
|
||||
fn get_new_version(&self) -> String {
|
||||
hbb_common::get_version_from_url(&*SOFTWARE_UPDATE_URL.lock().unwrap())
|
||||
get_new_version()
|
||||
}
|
||||
|
||||
fn get_version(&self) -> String {
|
||||
crate::VERSION.to_owned()
|
||||
get_version()
|
||||
}
|
||||
|
||||
fn get_app_name(&self) -> String {
|
||||
crate::get_app_name()
|
||||
get_app_name()
|
||||
}
|
||||
|
||||
fn get_software_ext(&self) -> String {
|
||||
#[cfg(windows)]
|
||||
let p = "exe";
|
||||
#[cfg(target_os = "macos")]
|
||||
let p = "dmg";
|
||||
#[cfg(target_os = "linux")]
|
||||
let p = "deb";
|
||||
p.to_owned()
|
||||
get_software_ext()
|
||||
}
|
||||
|
||||
fn get_software_store_path(&self) -> String {
|
||||
let mut p = std::env::temp_dir();
|
||||
let name = SOFTWARE_UPDATE_URL
|
||||
.lock()
|
||||
.unwrap()
|
||||
.split("/")
|
||||
.last()
|
||||
.map(|x| x.to_owned())
|
||||
.unwrap_or(crate::get_app_name());
|
||||
p.push(name);
|
||||
format!("{}.{}", p.to_string_lossy(), self.get_software_ext())
|
||||
get_software_store_path()
|
||||
}
|
||||
|
||||
fn create_shortcut(&self, _id: String) {
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::create_shortcut(&_id).ok();
|
||||
create_shortcut(_id)
|
||||
}
|
||||
|
||||
fn discover(&self) {
|
||||
@@ -711,79 +500,56 @@ impl UI {
|
||||
}
|
||||
|
||||
fn get_lan_peers(&self) -> String {
|
||||
serde_json::to_string(&config::LanPeers::load().peers).unwrap_or_default()
|
||||
let peers = get_lan_peers()
|
||||
.into_iter()
|
||||
.map(|(id, peer)| (id, peer.username, peer.hostname, peer.platform))
|
||||
.collect::<Vec<(String, String, String, String)>>();
|
||||
serde_json::to_string(&peers).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_uuid(&self) -> String {
|
||||
base64::encode(hbb_common::get_uuid())
|
||||
get_uuid()
|
||||
}
|
||||
|
||||
fn open_url(&self, url: String) {
|
||||
#[cfg(windows)]
|
||||
let p = "explorer";
|
||||
#[cfg(target_os = "macos")]
|
||||
let p = "open";
|
||||
#[cfg(target_os = "linux")]
|
||||
let p = if std::path::Path::new("/usr/bin/firefox").exists() {
|
||||
"firefox"
|
||||
} else {
|
||||
"xdg-open"
|
||||
};
|
||||
allow_err!(std::process::Command::new(p).arg(url).spawn());
|
||||
open_url(url)
|
||||
}
|
||||
|
||||
fn change_id(&self, id: String) {
|
||||
let status = self.3.clone();
|
||||
*status.lock().unwrap() = " ".to_owned();
|
||||
let old_id = self.get_id();
|
||||
std::thread::spawn(move || {
|
||||
*status.lock().unwrap() = change_id(id, old_id).to_owned();
|
||||
});
|
||||
change_id(id, old_id);
|
||||
}
|
||||
|
||||
fn post_request(&self, url: String, body: String, header: String) {
|
||||
let status = self.3.clone();
|
||||
*status.lock().unwrap() = " ".to_owned();
|
||||
std::thread::spawn(move || {
|
||||
*status.lock().unwrap() = match crate::post_request_sync(url, body, &header) {
|
||||
Err(err) => err.to_string(),
|
||||
Ok(text) => text,
|
||||
};
|
||||
});
|
||||
post_request(url, body, header)
|
||||
}
|
||||
|
||||
fn is_ok_change_id(&self) -> bool {
|
||||
machine_uid::get().is_ok()
|
||||
is_ok_change_id()
|
||||
}
|
||||
|
||||
fn get_async_job_status(&self) -> String {
|
||||
self.3.clone().lock().unwrap().clone()
|
||||
get_async_job_status()
|
||||
}
|
||||
|
||||
fn t(&self, name: String) -> String {
|
||||
crate::client::translate(name)
|
||||
t(name)
|
||||
}
|
||||
|
||||
fn is_xfce(&self) -> bool {
|
||||
crate::platform::is_xfce()
|
||||
is_xfce()
|
||||
}
|
||||
|
||||
fn get_api_server(&self) -> String {
|
||||
crate::get_api_server(
|
||||
self.get_option_("api-server"),
|
||||
self.get_option_("custom-rendezvous-server"),
|
||||
)
|
||||
get_api_server()
|
||||
}
|
||||
|
||||
fn has_hwcodec(&self) -> bool {
|
||||
#[cfg(not(feature = "hwcodec"))]
|
||||
return false;
|
||||
#[cfg(feature = "hwcodec")]
|
||||
return true;
|
||||
has_hwcodec()
|
||||
}
|
||||
|
||||
fn get_langs(&self) -> String {
|
||||
crate::lang::LANGS.to_string()
|
||||
get_langs()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ class SearchBar: Reactor.Component {
|
||||
}
|
||||
|
||||
event change $(input) (_, el) {
|
||||
this.onChange(el.value.trim());
|
||||
this.onChange(el.value.trim().toLowerCase());
|
||||
}
|
||||
|
||||
function onChange(v) {
|
||||
@@ -297,8 +297,13 @@ class SessionList: Reactor.Component {
|
||||
if (!p) return this.sessions;
|
||||
var tmp = [];
|
||||
this.sessions.map(function(s) {
|
||||
var name = s[4] || s.alias || s[0] || s.id || "";
|
||||
if (name.indexOf(p) >= 0) tmp.push(s);
|
||||
var name = (s[4] || s.alias || "").toLowerCase();
|
||||
var id = (s[0] || s.id || "").toLowerCase();
|
||||
var user = (s[1] || "").toLowerCase();
|
||||
var hostname = (s[2] || "").toLowerCase();
|
||||
if (name.indexOf(p) >= 0 || id.indexOf(p) >= 0 || user.indexOf(p) >= 0 || hostname.indexOf(p) >= 0) {
|
||||
tmp.push(s);
|
||||
}
|
||||
});
|
||||
return tmp;
|
||||
}
|
||||
@@ -316,7 +321,7 @@ class SessionList: Reactor.Component {
|
||||
<li #connect>{translate('Connect')}</li>
|
||||
<li #transfer>{translate('Transfer File')}</li>
|
||||
<li #tunnel>{translate('TCP Tunneling')}</li>
|
||||
{false && !handler.using_public_server() && <li #force-always-relay><span>{svg_checkmark}</span>{translate('Always connect via relay')}</li>}
|
||||
<li #force-always-relay><span>{svg_checkmark}</span>{translate('Always connect via relay')}</li>
|
||||
<li #rdp>RDP<EditRdpPort /></li>
|
||||
<li #wol>{translate('WOL')}</li>
|
||||
<div .separator />
|
||||
@@ -396,7 +401,6 @@ class SessionList: Reactor.Component {
|
||||
if (el) {
|
||||
var force = handler.get_peer_option(id, "force-always-relay");
|
||||
el.attributes.toggleClass("selected", force == "Y");
|
||||
el.attributes.toggleClass("line-through", force != "Y");
|
||||
}
|
||||
var conn = this.$(menu #connect);
|
||||
if (conn) {
|
||||
|
||||
85
src/ui/cm.rs
85
src/ui/cm.rs
@@ -1,3 +1,5 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::ipc::start_pa;
|
||||
use crate::ipc::{self, new_listener, Connection, Data};
|
||||
#[cfg(windows)]
|
||||
use clipboard::{
|
||||
@@ -159,7 +161,7 @@ impl ConnectionManager {
|
||||
id,
|
||||
file_num,
|
||||
mut files,
|
||||
overwrite_detection
|
||||
overwrite_detection,
|
||||
} => {
|
||||
// cm has no show_hidden context
|
||||
// dummy remote, show_hidden, is_remote
|
||||
@@ -436,7 +438,7 @@ impl sciter::EventHandler for ConnectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
enum ClipboardFileData {
|
||||
pub enum ClipboardFileData {
|
||||
#[cfg(windows)]
|
||||
Clip((i32, ipc::ClipbaordFile)),
|
||||
Enable((i32, bool)),
|
||||
@@ -538,86 +540,9 @@ async fn start_ipc(cm: ConnectionManager) {
|
||||
crate::platform::quit_gui();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn start_pa() {
|
||||
use crate::audio_service::AUDIO_DATA_SIZE_U8;
|
||||
|
||||
match new_listener("_pa").await {
|
||||
Ok(mut incoming) => {
|
||||
loop {
|
||||
if let Some(result) = incoming.next().await {
|
||||
match result {
|
||||
Ok(stream) => {
|
||||
let mut stream = Connection::new(stream);
|
||||
let mut device: String = "".to_owned();
|
||||
if let Some(Ok(Some(Data::Config((_, Some(x)))))) =
|
||||
stream.next_timeout2(1000).await
|
||||
{
|
||||
device = x;
|
||||
}
|
||||
if !device.is_empty() {
|
||||
device = crate::platform::linux::get_pa_source_name(&device);
|
||||
}
|
||||
if device.is_empty() {
|
||||
device = crate::platform::linux::get_pa_monitor();
|
||||
}
|
||||
if device.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let spec = pulse::sample::Spec {
|
||||
format: pulse::sample::Format::F32le,
|
||||
channels: 2,
|
||||
rate: crate::platform::PA_SAMPLE_RATE,
|
||||
};
|
||||
log::info!("pa monitor: {:?}", device);
|
||||
// systemctl --user status pulseaudio.service
|
||||
let mut buf: Vec<u8> = vec![0; AUDIO_DATA_SIZE_U8];
|
||||
match psimple::Simple::new(
|
||||
None, // Use the default server
|
||||
&crate::get_app_name(), // Our application’s name
|
||||
pulse::stream::Direction::Record, // We want a record stream
|
||||
Some(&device), // Use the default device
|
||||
"record", // Description of our stream
|
||||
&spec, // Our sample format
|
||||
None, // Use default channel map
|
||||
None, // Use default buffering attributes
|
||||
) {
|
||||
Ok(s) => loop {
|
||||
if let Ok(_) = s.read(&mut buf) {
|
||||
let out =
|
||||
if buf.iter().filter(|x| **x != 0).next().is_none() {
|
||||
vec![]
|
||||
} else {
|
||||
buf.clone()
|
||||
};
|
||||
if let Err(err) = stream.send_raw(out.into()).await {
|
||||
log::error!("Failed to send audio data:{}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("Could not create simple pulse: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Couldn't get pa client: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to start pa ipc server: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn start_clipboard_file(
|
||||
pub async fn start_clipboard_file(
|
||||
cm: ConnectionManager,
|
||||
mut rx: mpsc::UnboundedReceiver<ClipboardFileData>,
|
||||
) {
|
||||
|
||||
@@ -188,7 +188,8 @@ class JobTable: Reactor.Component {
|
||||
job.confirmed = true;
|
||||
return;
|
||||
}else if (job.type == "del-dir"){
|
||||
handler.remove_dir_all(job.id, job.path, job.is_remote);
|
||||
// TODO: include_hidden is always true
|
||||
handler.remove_dir_all(job.id, job.path, job.is_remote, true);
|
||||
job.confirmed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -25,8 +25,12 @@ use clipboard::{
|
||||
use enigo::{self, Enigo, KeyboardControllable};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::{Config, LocalConfig, PeerConfig},
|
||||
fs, log,
|
||||
config::{Config, LocalConfig, PeerConfig, TransferSerde},
|
||||
fs::{
|
||||
self, can_enable_overwrite_detection, get_job, get_string, new_send_confirm,
|
||||
DigestCheckResult, RemoveJobMeta, TransferJobMeta,
|
||||
},
|
||||
get_version_number, log,
|
||||
message_proto::{permission_info::Permission, *},
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::ConnType,
|
||||
@@ -54,6 +58,7 @@ use crate::{
|
||||
client::*,
|
||||
common::{self, check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL},
|
||||
};
|
||||
use errno;
|
||||
|
||||
type Video = AssetPtr<video_destination>;
|
||||
|
||||
@@ -208,7 +213,7 @@ impl sciter::EventHandler for Handler {
|
||||
fn read_remote_dir(String, bool);
|
||||
fn send_chat(String);
|
||||
fn switch_display(i32);
|
||||
fn remove_dir_all(i32, String, bool);
|
||||
fn remove_dir_all(i32, String, bool, bool);
|
||||
fn confirm_delete_files(i32, i32);
|
||||
fn set_no_confirm(i32);
|
||||
fn cancel_job(i32);
|
||||
@@ -1653,12 +1658,21 @@ impl Remote {
|
||||
async fn io_loop(&mut self, key: &str, token: &str) {
|
||||
let stop_clipboard = self.start_clipboard();
|
||||
let mut last_recv_time = Instant::now();
|
||||
let mut received = false;
|
||||
let conn_type = if self.handler.is_file_transfer() {
|
||||
ConnType::FILE_TRANSFER
|
||||
} else {
|
||||
ConnType::default()
|
||||
};
|
||||
match Client::start(&self.handler.id, key, token, conn_type).await {
|
||||
match Client::start(
|
||||
&self.handler.id,
|
||||
key,
|
||||
token,
|
||||
conn_type,
|
||||
self.handler.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((mut peer, direct)) => {
|
||||
SERVER_KEYBOARD_ENABLED.store(true, Ordering::SeqCst);
|
||||
SERVER_CLIPBOARD_ENABLED.store(true, Ordering::SeqCst);
|
||||
@@ -1681,11 +1695,13 @@ impl Remote {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::error!("Connection closed: {}", err);
|
||||
self.handler.set_force_relay(direct, received);
|
||||
self.handler.msgbox("error", "Connection Error", &err.to_string());
|
||||
break;
|
||||
}
|
||||
Ok(ref bytes) => {
|
||||
last_recv_time = Instant::now();
|
||||
received = true;
|
||||
self.data_count.fetch_add(bytes.len(), Ordering::Relaxed);
|
||||
if !self.handle_msg_from_peer(bytes, &mut peer).await {
|
||||
break
|
||||
@@ -2093,7 +2109,7 @@ impl Remote {
|
||||
}
|
||||
}
|
||||
}
|
||||
Data::RemoveDirAll((id, path, is_remote)) => {
|
||||
Data::RemoveDirAll((id, path, is_remote, include_hidden)) => {
|
||||
let sep = self.handler.get_path_sep(is_remote);
|
||||
if is_remote {
|
||||
let mut msg_out = Message::new();
|
||||
@@ -2101,7 +2117,7 @@ impl Remote {
|
||||
file_action.set_all_files(ReadAllFiles {
|
||||
id,
|
||||
path: path.clone(),
|
||||
include_hidden: true,
|
||||
include_hidden,
|
||||
..Default::default()
|
||||
});
|
||||
msg_out.set_file_action(file_action);
|
||||
@@ -2109,7 +2125,7 @@ impl Remote {
|
||||
self.remove_jobs
|
||||
.insert(id, RemoveJob::new(Vec::new(), path, sep, is_remote));
|
||||
} else {
|
||||
match fs::get_recursive_files(&path, true) {
|
||||
match fs::get_recursive_files(&path, include_hidden) {
|
||||
Ok(entries) => {
|
||||
let m = make_fd(id, &entries, true);
|
||||
self.handler.call("updateFolderFiles", &make_args!(m));
|
||||
@@ -2270,18 +2286,18 @@ impl Remote {
|
||||
|
||||
async fn send_opts_after_login(&self, peer: &mut Stream) {
|
||||
if let Some(opts) = self
|
||||
.handler
|
||||
.lc
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_option_message_after_login()
|
||||
{
|
||||
let mut misc = Misc::new();
|
||||
misc.set_option(opts);
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
allow_err!(peer.send(&msg_out).await);
|
||||
}
|
||||
.handler
|
||||
.lc
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_option_message_after_login()
|
||||
{
|
||||
let mut misc = Misc::new();
|
||||
misc.set_option(opts);
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
allow_err!(peer.send(&msg_out).await);
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool {
|
||||
@@ -2892,6 +2908,24 @@ impl Interface for Handler {
|
||||
handle_test_delay(t, peer).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_force_relay(&mut self, direct: bool, received: bool) {
|
||||
let mut lc = self.lc.write().unwrap();
|
||||
lc.force_relay = false;
|
||||
if direct && !received {
|
||||
let errno = errno::errno().0;
|
||||
log::info!("errno is {}", errno);
|
||||
// TODO: check mac and ios
|
||||
if cfg!(windows) && errno == 10054 || !cfg!(windows) && errno == 104 {
|
||||
lc.force_relay = true;
|
||||
lc.set_option("force-always-relay".to_owned(), "Y".to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_force_relay(&self) -> bool {
|
||||
self.lc.read().unwrap().force_relay
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
|
||||
874
src/ui_interface.rs
Normal file
874
src/ui_interface.rs
Normal file
@@ -0,0 +1,874 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
process::Child,
|
||||
sync::{Arc, Mutex},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
use hbb_common::password_security;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
|
||||
futures::future::join_all,
|
||||
log,
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::*,
|
||||
sleep,
|
||||
tcp::FramedStream,
|
||||
tokio::{self, sync::mpsc, time},
|
||||
};
|
||||
|
||||
use crate::common::SOFTWARE_UPDATE_URL;
|
||||
use crate::ipc;
|
||||
|
||||
type Message = RendezvousMessage;
|
||||
|
||||
pub type Childs = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
|
||||
type Status = (i32, bool, i64, String); // (status_num, key_confirmed, mouse_time, id)
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref CHILDS : Childs = Default::default();
|
||||
pub static ref UI_STATUS : Arc<Mutex<Status>> = Arc::new(Mutex::new((0, false, 0, "".to_owned())));
|
||||
pub static ref OPTIONS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(Config::get_options()));
|
||||
pub static ref ASYNC_JOB_STATUS : Arc<Mutex<String>> = Default::default();
|
||||
pub static ref TEMPORARY_PASSWD : Arc<Mutex<String>> = Arc::new(Mutex::new("".to_owned()));
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref SENDER : Mutex<mpsc::UnboundedSender<ipc::Data>> = Mutex::new(check_connect_status(true));
|
||||
}
|
||||
|
||||
pub fn recent_sessions_updated() -> bool {
|
||||
let mut childs = CHILDS.lock().unwrap();
|
||||
if childs.0 {
|
||||
childs.0 = false;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id() -> String {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
return Config::get_id();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
return ipc::get_id();
|
||||
}
|
||||
|
||||
pub fn get_remote_id() -> String {
|
||||
LocalConfig::get_remote_id()
|
||||
}
|
||||
|
||||
pub fn set_remote_id(id: String) {
|
||||
LocalConfig::set_remote_id(&id);
|
||||
}
|
||||
|
||||
pub fn goto_install() {
|
||||
allow_err!(crate::run_me(vec!["--install"]));
|
||||
}
|
||||
|
||||
pub fn install_me(_options: String, _path: String, silent: bool, debug: bool) {
|
||||
#[cfg(windows)]
|
||||
std::thread::spawn(move || {
|
||||
allow_err!(crate::platform::windows::install_me(
|
||||
&_options, _path, silent, debug
|
||||
));
|
||||
std::process::exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_me(_path: String) {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
std::process::Command::new("pkexec")
|
||||
.args(&["apt", "install", "-f", &_path])
|
||||
.spawn()
|
||||
.ok();
|
||||
std::fs::remove_file(&_path).ok();
|
||||
crate::run_me(Vec::<&str>::new()).ok();
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut path = _path;
|
||||
if path.is_empty() {
|
||||
if let Ok(tmp) = std::env::current_exe() {
|
||||
path = tmp.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
std::process::Command::new(path)
|
||||
.arg("--update")
|
||||
.spawn()
|
||||
.ok();
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_without_install() {
|
||||
crate::run_me(vec!["--noinstall"]).ok();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
pub fn show_run_without_install() -> bool {
|
||||
let mut it = std::env::args();
|
||||
if let Some(tmp) = it.next() {
|
||||
if crate::is_setup(&tmp) {
|
||||
return it.next() == None;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn has_rendezvous_service() -> bool {
|
||||
#[cfg(all(windows, feature = "hbbs"))]
|
||||
return crate::platform::is_win_server() && crate::platform::windows::get_license().is_some();
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn get_license() -> String {
|
||||
#[cfg(windows)]
|
||||
if let Some(lic) = crate::platform::windows::get_license() {
|
||||
return format!(
|
||||
"<br /> Key: {} <br /> Host: {} Api: {}",
|
||||
lic.key, lic.host, lic.api
|
||||
);
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn get_option(key: String) -> String {
|
||||
get_option_(&key)
|
||||
// #[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
// return Config::get_option(&key);
|
||||
// #[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
// return get_option_(&key);
|
||||
}
|
||||
|
||||
fn get_option_(key: &str) -> String {
|
||||
let map = OPTIONS.lock().unwrap();
|
||||
if let Some(v) = map.get(key) {
|
||||
v.to_owned()
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_local_option(key: String) -> String {
|
||||
LocalConfig::get_option(&key)
|
||||
}
|
||||
|
||||
pub fn set_local_option(key: String, value: String) {
|
||||
LocalConfig::set_option(key, value);
|
||||
}
|
||||
|
||||
pub fn peer_has_password(id: String) -> bool {
|
||||
!PeerConfig::load(&id).password.is_empty()
|
||||
}
|
||||
|
||||
pub fn forget_password(id: String) {
|
||||
let mut c = PeerConfig::load(&id);
|
||||
c.password.clear();
|
||||
c.store(&id);
|
||||
}
|
||||
|
||||
pub fn get_peer_option(id: String, name: String) -> String {
|
||||
let c = PeerConfig::load(&id);
|
||||
c.options.get(&name).unwrap_or(&"".to_owned()).to_owned()
|
||||
}
|
||||
|
||||
pub fn set_peer_option(id: String, name: String, value: String) {
|
||||
let mut c = PeerConfig::load(&id);
|
||||
if value.is_empty() {
|
||||
c.options.remove(&name);
|
||||
} else {
|
||||
c.options.insert(name, value);
|
||||
}
|
||||
c.store(&id);
|
||||
}
|
||||
|
||||
pub fn using_public_server() -> bool {
|
||||
crate::get_custom_rendezvous_server(get_option_("custom-rendezvous-server")).is_empty()
|
||||
}
|
||||
|
||||
pub fn get_options() -> String {
|
||||
let options = OPTIONS.lock().unwrap();
|
||||
let mut m = serde_json::Map::new();
|
||||
for (k, v) in options.iter() {
|
||||
m.insert(k.into(), v.to_owned().into());
|
||||
}
|
||||
serde_json::to_string(&m).unwrap()
|
||||
}
|
||||
|
||||
pub fn test_if_valid_server(host: String) -> String {
|
||||
hbb_common::socket_client::test_if_valid_server(&host)
|
||||
}
|
||||
|
||||
pub fn get_sound_inputs() -> Vec<String> {
|
||||
let mut a = Vec::new();
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// TODO TEST
|
||||
fn get_sound_inputs_() -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
use cpal::traits::{DeviceTrait, HostTrait};
|
||||
let host = cpal::default_host();
|
||||
if let Ok(devices) = host.devices() {
|
||||
for device in devices {
|
||||
if device.default_input_config().is_err() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(name) = device.name() {
|
||||
out.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
let inputs = Arc::new(Mutex::new(Vec::new()));
|
||||
let cloned = inputs.clone();
|
||||
// can not call below in UI thread, because conflict with sciter sound com initialization
|
||||
std::thread::spawn(move || *cloned.lock().unwrap() = get_sound_inputs_())
|
||||
.join()
|
||||
.ok();
|
||||
for name in inputs.lock().unwrap().drain(..) {
|
||||
a.push(name);
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "linux")] // TODO
|
||||
{
|
||||
let inputs: Vec<String> = crate::platform::linux::get_pa_sources()
|
||||
.drain(..)
|
||||
.map(|x| x.1)
|
||||
.collect();
|
||||
|
||||
for name in inputs {
|
||||
a.push(name);
|
||||
}
|
||||
}
|
||||
a
|
||||
}
|
||||
|
||||
pub fn set_options(m: HashMap<String, String>) {
|
||||
*OPTIONS.lock().unwrap() = m.clone();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
ipc::set_options(m).ok();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
Config::set_options(m);
|
||||
}
|
||||
|
||||
pub fn set_option(key: String, value: String) {
|
||||
let mut options = OPTIONS.lock().unwrap();
|
||||
#[cfg(target_os = "macos")]
|
||||
if &key == "stop-service" {
|
||||
let is_stop = value == "Y";
|
||||
if is_stop && crate::platform::macos::uninstall() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if value.is_empty() {
|
||||
options.remove(&key);
|
||||
} else {
|
||||
options.insert(key.clone(), value.clone());
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
ipc::set_options(options.clone()).ok();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
Config::set_option(key, value);
|
||||
}
|
||||
|
||||
pub fn install_path() -> String {
|
||||
#[cfg(windows)]
|
||||
return crate::platform::windows::get_install_info().1;
|
||||
#[cfg(not(windows))]
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
pub fn get_socks() -> Vec<String> {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
return Vec::new();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
let s = ipc::get_socks();
|
||||
match s {
|
||||
None => Vec::new(),
|
||||
Some(s) => {
|
||||
let mut v = Vec::new();
|
||||
v.push(s.proxy);
|
||||
v.push(s.username);
|
||||
v.push(s.password);
|
||||
v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
password,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn is_installed() -> bool {
|
||||
crate::platform::is_installed()
|
||||
}
|
||||
|
||||
pub fn is_rdp_service_open() -> bool {
|
||||
#[cfg(windows)]
|
||||
return is_installed() && crate::platform::windows::is_rdp_service_open();
|
||||
#[cfg(not(windows))]
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn is_share_rdp() -> bool {
|
||||
#[cfg(windows)]
|
||||
return crate::platform::windows::is_share_rdp();
|
||||
#[cfg(not(windows))]
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn set_share_rdp(_enable: bool) {
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::set_share_rdp(_enable);
|
||||
}
|
||||
|
||||
pub fn is_installed_lower_version() -> bool {
|
||||
#[cfg(not(windows))]
|
||||
return false;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let installed_version = crate::platform::windows::get_installed_version();
|
||||
let a = hbb_common::get_version_number(crate::VERSION);
|
||||
let b = hbb_common::get_version_number(&installed_version);
|
||||
return a > b;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn closing(x: i32, y: i32, w: i32, h: i32) {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::server::input_service::fix_key_down_timeout_at_exit();
|
||||
LocalConfig::set_size(x, y, w, h);
|
||||
}
|
||||
|
||||
pub fn get_size() -> Vec<i32> {
|
||||
let s = LocalConfig::get_size();
|
||||
let mut v = Vec::new();
|
||||
v.push(s.0);
|
||||
v.push(s.1);
|
||||
v.push(s.2);
|
||||
v.push(s.3);
|
||||
v
|
||||
}
|
||||
|
||||
pub fn get_mouse_time() -> f64 {
|
||||
let ui_status = UI_STATUS.lock().unwrap();
|
||||
let res = ui_status.2 as f64;
|
||||
return res;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn check_mouse_time() {
|
||||
let sender = SENDER.lock().unwrap();
|
||||
allow_err!(sender.send(ipc::Data::MouseMoveTime(0)));
|
||||
}
|
||||
|
||||
pub fn get_connect_status() -> Status {
|
||||
let ui_statue = UI_STATUS.lock().unwrap();
|
||||
let res = ui_statue.clone();
|
||||
res
|
||||
}
|
||||
|
||||
pub fn temporary_password() -> String {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
return password_security::temporary_password();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
return TEMPORARY_PASSWD.lock().unwrap().clone();
|
||||
}
|
||||
|
||||
pub fn update_temporary_password() {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
password_security::update_temporary_password();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
allow_err!(ipc::update_temporary_password());
|
||||
}
|
||||
|
||||
pub fn permanent_password() -> String {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
return Config::get_permanent_password();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
return ipc::get_permanent_password();
|
||||
}
|
||||
|
||||
pub fn set_permanent_password(password: String) {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
Config::set_permanent_password(&password);
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
allow_err!(ipc::set_permanent_password(password));
|
||||
}
|
||||
|
||||
pub fn get_peer(id: String) -> PeerConfig {
|
||||
PeerConfig::load(&id)
|
||||
}
|
||||
|
||||
pub fn get_fav() -> Vec<String> {
|
||||
LocalConfig::get_fav()
|
||||
}
|
||||
|
||||
pub fn store_fav(fav: Vec<String>) {
|
||||
LocalConfig::set_fav(fav);
|
||||
}
|
||||
|
||||
pub fn get_recent_sessions() -> Vec<(String, SystemTime, PeerConfig)> {
|
||||
PeerConfig::peers()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
|
||||
pub fn get_icon() -> String {
|
||||
crate::get_icon()
|
||||
}
|
||||
|
||||
pub fn remove_peer(id: String) {
|
||||
PeerConfig::remove(&id);
|
||||
}
|
||||
|
||||
pub fn new_remote(id: String, remote_type: String) {
|
||||
let mut lock = CHILDS.lock().unwrap();
|
||||
let args = vec![format!("--{}", remote_type), id.clone()];
|
||||
let key = (id.clone(), remote_type.clone());
|
||||
if let Some(c) = lock.1.get_mut(&key) {
|
||||
if let Ok(Some(_)) = c.try_wait() {
|
||||
lock.1.remove(&key);
|
||||
} else {
|
||||
if remote_type == "rdp" {
|
||||
allow_err!(c.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
c.try_wait().ok();
|
||||
lock.1.remove(&key);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
match crate::run_me(args) {
|
||||
Ok(child) => {
|
||||
lock.1.insert(key, child);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to spawn remote: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_process_trusted(_prompt: bool) -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
return crate::platform::macos::is_process_trusted(_prompt);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn is_can_screen_recording(_prompt: bool) -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
return crate::platform::macos::is_can_screen_recording(_prompt);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn is_installed_daemon(_prompt: bool) -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
return crate::platform::macos::is_installed_daemon(_prompt);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn get_error() -> String {
|
||||
#[cfg(not(any(feature = "cli")))]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let dtype = crate::platform::linux::get_display_server();
|
||||
if "wayland" == dtype {
|
||||
return "".to_owned();
|
||||
}
|
||||
if dtype != "x11" {
|
||||
return format!(
|
||||
"{} {}, {}",
|
||||
t("Unsupported display server ".to_owned()),
|
||||
dtype,
|
||||
t("x11 expected".to_owned()),
|
||||
);
|
||||
}
|
||||
}
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
pub fn is_login_wayland() -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
return crate::platform::linux::is_login_wayland();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn fix_login_wayland() {
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::platform::linux::fix_login_wayland();
|
||||
}
|
||||
|
||||
pub fn current_is_wayland() -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
return crate::platform::linux::current_is_wayland();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn modify_default_login() -> String {
|
||||
#[cfg(target_os = "linux")]
|
||||
return crate::platform::linux::modify_default_login();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
pub fn get_software_update_url() -> String {
|
||||
SOFTWARE_UPDATE_URL.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn get_new_version() -> String {
|
||||
hbb_common::get_version_from_url(&*SOFTWARE_UPDATE_URL.lock().unwrap())
|
||||
}
|
||||
|
||||
pub fn get_version() -> String {
|
||||
crate::VERSION.to_owned()
|
||||
}
|
||||
|
||||
pub fn get_app_name() -> String {
|
||||
crate::get_app_name()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn get_software_ext() -> String {
|
||||
#[cfg(windows)]
|
||||
let p = "exe";
|
||||
#[cfg(target_os = "macos")]
|
||||
let p = "dmg";
|
||||
#[cfg(target_os = "linux")]
|
||||
let p = "deb";
|
||||
p.to_owned()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn get_software_store_path() -> String {
|
||||
let mut p = std::env::temp_dir();
|
||||
let name = SOFTWARE_UPDATE_URL
|
||||
.lock()
|
||||
.unwrap()
|
||||
.split("/")
|
||||
.last()
|
||||
.map(|x| x.to_owned())
|
||||
.unwrap_or(crate::get_app_name());
|
||||
p.push(name);
|
||||
format!("{}.{}", p.to_string_lossy(), get_software_ext())
|
||||
}
|
||||
|
||||
pub fn create_shortcut(_id: String) {
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::create_shortcut(&_id).ok();
|
||||
}
|
||||
|
||||
pub fn discover() {
|
||||
std::thread::spawn(move || {
|
||||
allow_err!(crate::lan::discover());
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_lan_peers() -> Vec<(String, config::PeerInfoSerde)> {
|
||||
config::LanPeers::load()
|
||||
.peers
|
||||
.iter()
|
||||
.map(|peer| {
|
||||
(
|
||||
peer.id.clone(),
|
||||
config::PeerInfoSerde {
|
||||
username: peer.username.clone(),
|
||||
hostname: peer.hostname.clone(),
|
||||
platform: peer.platform.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_uuid() -> String {
|
||||
base64::encode(hbb_common::get_uuid())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
|
||||
pub fn open_url(url: String) {
|
||||
#[cfg(windows)]
|
||||
let p = "explorer";
|
||||
#[cfg(target_os = "macos")]
|
||||
let p = "open";
|
||||
#[cfg(target_os = "linux")]
|
||||
let p = if std::path::Path::new("/usr/bin/firefox").exists() {
|
||||
"firefox"
|
||||
} else {
|
||||
"xdg-open"
|
||||
};
|
||||
allow_err!(std::process::Command::new(p).arg(url).spawn());
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn change_id(id: String) {
|
||||
*ASYNC_JOB_STATUS.lock().unwrap() = " ".to_owned();
|
||||
let old_id = get_id();
|
||||
std::thread::spawn(move || {
|
||||
*ASYNC_JOB_STATUS.lock().unwrap() = change_id_(id, old_id).to_owned();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn post_request(url: String, body: String, header: String) {
|
||||
*ASYNC_JOB_STATUS.lock().unwrap() = " ".to_owned();
|
||||
std::thread::spawn(move || {
|
||||
*ASYNC_JOB_STATUS.lock().unwrap() = match crate::post_request_sync(url, body, &header) {
|
||||
Err(err) => err.to_string(),
|
||||
Ok(text) => text,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn is_ok_change_id() -> bool {
|
||||
machine_uid::get().is_ok()
|
||||
}
|
||||
|
||||
pub fn get_async_job_status() -> String {
|
||||
ASYNC_JOB_STATUS.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
|
||||
pub fn t(name: String) -> String {
|
||||
crate::client::translate(name)
|
||||
}
|
||||
|
||||
pub fn get_langs() -> String {
|
||||
crate::lang::LANGS.to_string()
|
||||
}
|
||||
|
||||
pub fn is_xfce() -> bool {
|
||||
crate::platform::is_xfce()
|
||||
}
|
||||
|
||||
pub fn get_api_server() -> String {
|
||||
crate::get_api_server(
|
||||
get_option_("api-server"),
|
||||
get_option_("custom-rendezvous-server"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn has_hwcodec() -> bool {
|
||||
#[cfg(not(feature = "hwcodec"))]
|
||||
return false;
|
||||
#[cfg(feature = "hwcodec")]
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn check_super_user_permission() -> bool {
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
return crate::platform::check_super_user_permission().unwrap_or(false);
|
||||
#[cfg(not(any(windows, target_os = "linux")))]
|
||||
true
|
||||
}
|
||||
|
||||
pub fn check_zombie(childs: Childs) {
|
||||
let mut deads = Vec::new();
|
||||
loop {
|
||||
let mut lock = childs.lock().unwrap();
|
||||
let mut n = 0;
|
||||
for (id, c) in lock.1.iter_mut() {
|
||||
if let Ok(Some(_)) = c.try_wait() {
|
||||
deads.push(id.clone());
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
for ref id in deads.drain(..) {
|
||||
lock.1.remove(id);
|
||||
}
|
||||
if n > 0 {
|
||||
lock.0 = true;
|
||||
}
|
||||
drop(lock);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) 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));
|
||||
tx
|
||||
}
|
||||
|
||||
// notice: avoiding create ipc connecton repeatly,
|
||||
// because windows named pipe has serious memory leak issue.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc::Data>) {
|
||||
let mut key_confirmed = false;
|
||||
let mut rx = rx;
|
||||
let mut mouse_time = 0;
|
||||
let mut id = "".to_owned();
|
||||
loop {
|
||||
if let Ok(mut c) = ipc::connect(1000, "").await {
|
||||
let mut timer = time::interval(time::Duration::from_secs(1));
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = c.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::error!("ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(ipc::Data::MouseMoveTime(v))) => {
|
||||
mouse_time = v;
|
||||
UI_STATUS.lock().unwrap().2 = v;
|
||||
}
|
||||
Ok(Some(ipc::Data::Options(Some(v)))) => {
|
||||
*OPTIONS.lock().unwrap() = v
|
||||
}
|
||||
Ok(Some(ipc::Data::Config((name, Some(value))))) => {
|
||||
if name == "id" {
|
||||
id = value;
|
||||
} else if name == "temporary-password" {
|
||||
*TEMPORARY_PASSWD.lock().unwrap() = value;
|
||||
}
|
||||
}
|
||||
Ok(Some(ipc::Data::OnlineStatus(Some((mut x, c))))) => {
|
||||
if x > 0 {
|
||||
x = 1
|
||||
}
|
||||
key_confirmed = c;
|
||||
*UI_STATUS.lock().unwrap() = (x as _, key_confirmed, mouse_time, id.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some(data) = rx.recv() => {
|
||||
allow_err!(c.send(&data).await);
|
||||
}
|
||||
_ = timer.tick() => {
|
||||
c.send(&ipc::Data::OnlineStatus(None)).await.ok();
|
||||
c.send(&ipc::Data::Options(None)).await.ok();
|
||||
c.send(&ipc::Data::Config(("id".to_owned(), None))).await.ok();
|
||||
c.send(&ipc::Data::Config(("temporary-password".to_owned(), None))).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !reconnect {
|
||||
OPTIONS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert("ipc-closed".to_owned(), "Y".to_owned());
|
||||
break;
|
||||
}
|
||||
*UI_STATUS.lock().unwrap() = (-1, key_confirmed, mouse_time, id.clone());
|
||||
sleep(1.).await;
|
||||
}
|
||||
}
|
||||
|
||||
const INVALID_FORMAT: &'static str = "Invalid format";
|
||||
const UNKNOWN_ERROR: &'static str = "Unknown error";
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn change_id_(id: String, old_id: String) -> &'static str {
|
||||
if !hbb_common::is_valid_custom_id(&id) {
|
||||
return INVALID_FORMAT;
|
||||
}
|
||||
let uuid = machine_uid::get().unwrap_or("".to_owned());
|
||||
if uuid.is_empty() {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
let rendezvous_servers = crate::ipc::get_rendezvous_servers(1_000).await;
|
||||
let mut futs = Vec::new();
|
||||
let err: Arc<Mutex<&str>> = Default::default();
|
||||
for rendezvous_server in rendezvous_servers {
|
||||
let err = err.clone();
|
||||
let id = id.to_owned();
|
||||
let uuid = uuid.clone();
|
||||
let old_id = old_id.clone();
|
||||
futs.push(tokio::spawn(async move {
|
||||
let tmp = check_id(rendezvous_server, old_id, id, uuid).await;
|
||||
if !tmp.is_empty() {
|
||||
*err.lock().unwrap() = tmp;
|
||||
}
|
||||
}));
|
||||
}
|
||||
join_all(futs).await;
|
||||
let err = *err.lock().unwrap();
|
||||
if err.is_empty() {
|
||||
crate::ipc::set_config_async("id", id.to_owned()).await.ok();
|
||||
}
|
||||
err
|
||||
}
|
||||
|
||||
async fn check_id(
|
||||
rendezvous_server: String,
|
||||
old_id: String,
|
||||
id: String,
|
||||
uuid: String,
|
||||
) -> &'static str {
|
||||
let any_addr = Config::get_any_listen_addr();
|
||||
if let Ok(mut socket) = FramedStream::new(
|
||||
crate::check_port(rendezvous_server, RENDEZVOUS_PORT),
|
||||
any_addr,
|
||||
RENDEZVOUS_TIMEOUT,
|
||||
)
|
||||
.await
|
||||
{
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_register_pk(RegisterPk {
|
||||
old_id,
|
||||
id,
|
||||
uuid: uuid.into(),
|
||||
..Default::default()
|
||||
});
|
||||
let mut ok = false;
|
||||
if socket.send(&msg_out).await.is_ok() {
|
||||
if let Some(Ok(bytes)) = socket.next_timeout(3_000).await {
|
||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::RegisterPkResponse(rpr)) => {
|
||||
match rpr.result.enum_value_or_default() {
|
||||
register_pk_response::Result::OK => {
|
||||
ok = true;
|
||||
}
|
||||
register_pk_response::Result::ID_EXISTS => {
|
||||
return "Not available";
|
||||
}
|
||||
register_pk_response::Result::TOO_FREQUENT => {
|
||||
return "Too frequent";
|
||||
}
|
||||
register_pk_response::Result::NOT_SUPPORT => {
|
||||
return "server_not_support";
|
||||
}
|
||||
register_pk_response::Result::INVALID_ID_FORMAT => {
|
||||
return INVALID_FORMAT;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
} else {
|
||||
return "Failed to connect to rendezvous server";
|
||||
}
|
||||
""
|
||||
}
|
||||
Reference in New Issue
Block a user