Merge branch 'rustdesk/master'

This commit is contained in:
Asura
2022-08-27 09:55:27 +08:00
156 changed files with 20300 additions and 3415 deletions

View File

@@ -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]

View File

@@ -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)));
}
}

View File

@@ -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);

View File

@@ -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
View 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
View 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()
}
}

View File

@@ -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 applications 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 {

View File

@@ -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(())
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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"),

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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;

View 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);
}
}

View File

@@ -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()
}
}

View File

@@ -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))
}

View File

@@ -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);
}
}

View File

@@ -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");
}

View File

@@ -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);
},
);
}
}

View File

@@ -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) {

View File

@@ -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
View File

@@ -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()
}
}

View File

@@ -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) {

View File

@@ -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 applications 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>,
) {

View File

@@ -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;
}

View File

@@ -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
View 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";
}
""
}