This commit is contained in:
Asura
2022-07-20 19:51:09 -07:00
110 changed files with 4942 additions and 1487 deletions

View File

@@ -28,6 +28,7 @@ use hbb_common::{
log,
message_proto::{option_message::BoolOption, *},
protobuf::Message as _,
rand,
rendezvous_proto::*,
socket_client,
sodiumoxide::crypto::{box_, secretbox, sign},
@@ -148,11 +149,25 @@ impl Client {
true,
));
}
let rendezvous_server = crate::get_rendezvous_server(1_000).await;
log::info!("rendezvous server: {}", rendezvous_server);
let (mut rendezvous_server, servers, contained) = crate::get_rendezvous_server(1_000).await;
let mut socket =
socket_client::connect_tcp(&*rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT).await?;
socket_client::connect_tcp(&*rendezvous_server, any_addr, RENDEZVOUS_TIMEOUT).await;
debug_assert!(!servers.contains(&rendezvous_server));
if socket.is_err() && !servers.is_empty() {
log::info!("try the other servers: {:?}", servers);
for server in servers {
socket = socket_client::connect_tcp(&*server, any_addr, RENDEZVOUS_TIMEOUT).await;
if socket.is_ok() {
rendezvous_server = server;
break;
}
}
crate::refresh_rendezvous_server();
} else if !contained {
crate::refresh_rendezvous_server();
}
log::info!("rendezvous server: {}", rendezvous_server);
let mut socket = socket?;
let my_addr = socket.local_addr();
let mut signed_id_pk = Vec::new();
let mut relay_server = "".to_owned();
@@ -165,7 +180,7 @@ impl Client {
for i in 1..=3 {
log::info!("#{} punch attempt with {}, id: {}", i, my_addr, peer);
let mut msg_out = RendezvousMessage::new();
use hbb_common::protobuf::ProtobufEnum;
use hbb_common::protobuf::Enum;
let nat_type = NatType::from_i32(my_nat_type).unwrap_or(NatType::UNKNOWN_NAT);
msg_out.set_punch_hole_request(PunchHoleRequest {
id: peer.to_owned(),
@@ -179,7 +194,7 @@ impl Client {
if let Some(Ok(bytes)) = socket.next_timeout(i * 6000).await {
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
match msg_in.union {
Some(rendezvous_message::Union::punch_hole_response(ph)) => {
Some(rendezvous_message::Union::PunchHoleResponse(ph)) => {
if ph.socket_addr.is_empty() {
if !ph.other_failure.is_empty() {
bail!(ph.other_failure);
@@ -199,8 +214,8 @@ impl Client {
}
}
} else {
peer_nat_type = ph.get_nat_type();
is_local = ph.get_is_local();
peer_nat_type = ph.nat_type();
is_local = ph.is_local();
signed_id_pk = ph.pk;
relay_server = ph.relay_server;
peer_addr = AddrMangle::decode(&ph.socket_addr);
@@ -208,13 +223,13 @@ impl Client {
break;
}
}
Some(rendezvous_message::Union::relay_response(rr)) => {
Some(rendezvous_message::Union::RelayResponse(rr)) => {
log::info!(
"relay requested from peer, time used: {:?}, relay_server: {}",
start.elapsed(),
rr.relay_server
);
signed_id_pk = rr.get_pk().into();
signed_id_pk = rr.pk().into();
let mut conn =
Self::create_relay(peer, rr.uuid, rr.relay_server, key, conn_type)
.await?;
@@ -383,7 +398,7 @@ impl Client {
Some(res) => {
let bytes = res?;
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
if let Some(message::Union::signed_id(si)) = msg_in.union {
if let Some(message::Union::SignedId(si)) = msg_in.union {
if let Ok((id, their_pk_b)) = decode_id_pk(&si.id, &sign_pk) {
if id == peer_id {
let their_pk_b = box_::PublicKey(their_pk_b);
@@ -466,7 +481,7 @@ impl Client {
socket.send(&msg_out).await?;
if let Some(Ok(bytes)) = socket.next_timeout(CONNECT_TIMEOUT).await {
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
if let Some(rendezvous_message::Union::relay_response(rs)) = msg_in.union {
if let Some(rendezvous_message::Union::RelayResponse(rs)) = msg_in.union {
if !rs.refuse_reason.is_empty() {
bail!(rs.refuse_reason);
}
@@ -768,6 +783,7 @@ pub struct LoginConfigHandler {
pub version: i64,
pub conn_id: i32,
features: Option<Features>,
session_id: u64,
}
impl Deref for LoginConfigHandler {
@@ -791,6 +807,7 @@ impl LoginConfigHandler {
let config = self.load_config();
self.remember = !config.password.is_empty();
self.config = config;
self.session_id = rand::random();
}
pub fn should_auto_login(&self) -> String {
@@ -1126,6 +1143,7 @@ impl LoginConfigHandler {
my_id,
my_name: crate::username(),
option: self.get_option_message(true).into(),
session_id: self.session_id,
..Default::default()
};
if self.is_file_transfer {

View File

@@ -69,9 +69,9 @@ pub enum CodecFormat {
impl From<&VideoFrame> for CodecFormat {
fn from(it: &VideoFrame) -> Self {
match it.union {
Some(video_frame::Union::vp9s(_)) => CodecFormat::VP9,
Some(video_frame::Union::h264s(_)) => CodecFormat::H264,
Some(video_frame::Union::h265s(_)) => CodecFormat::H265,
Some(video_frame::Union::Vp9s(_)) => CodecFormat::VP9,
Some(video_frame::Union::H264s(_)) => CodecFormat::H264,
Some(video_frame::Union::H265s(_)) => CodecFormat::H265,
_ => CodecFormat::Unknown,
}
}

View File

@@ -17,8 +17,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
});
}
Message {
union: Some(message::Union::cliprdr(Cliprdr {
union: Some(cliprdr::Union::format_list(CliprdrServerFormatList {
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FormatList(CliprdrServerFormatList {
conn_id,
formats,
..Default::default()
@@ -29,8 +29,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
}
}
ClipbaordFile::ServerFormatListResponse { conn_id, msg_flags } => Message {
union: Some(message::Union::cliprdr(Cliprdr {
union: Some(cliprdr::Union::format_list_response(
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FormatListResponse(
CliprdrServerFormatListResponse {
conn_id,
msg_flags,
@@ -45,8 +45,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
conn_id,
requested_format_id,
} => Message {
union: Some(message::Union::cliprdr(Cliprdr {
union: Some(cliprdr::Union::format_data_request(
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FormatDataRequest(
CliprdrServerFormatDataRequest {
conn_id,
requested_format_id,
@@ -62,8 +62,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
msg_flags,
format_data,
} => Message {
union: Some(message::Union::cliprdr(Cliprdr {
union: Some(cliprdr::Union::format_data_response(
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FormatDataResponse(
CliprdrServerFormatDataResponse {
conn_id,
msg_flags,
@@ -86,8 +86,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
have_clip_data_id,
clip_data_id,
} => Message {
union: Some(message::Union::cliprdr(Cliprdr {
union: Some(cliprdr::Union::file_contents_request(
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FileContentsRequest(
CliprdrFileContentsRequest {
conn_id,
stream_id,
@@ -111,8 +111,8 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
stream_id,
requested_data,
} => Message {
union: Some(message::Union::cliprdr(Cliprdr {
union: Some(cliprdr::Union::file_contents_response(
union: Some(message::Union::Cliprdr(Cliprdr {
union: Some(cliprdr::Union::FileContentsResponse(
CliprdrFileContentsResponse {
conn_id,
msg_flags,
@@ -130,7 +130,7 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipbaordFile> {
match msg.union {
Some(cliprdr::Union::format_list(data)) => {
Some(cliprdr::Union::FormatList(data)) => {
let mut format_list: Vec<(i32, String)> = Vec::new();
for v in data.formats.iter() {
format_list.push((v.id, v.format.clone()));
@@ -140,26 +140,26 @@ pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipbaordFile> {
format_list,
})
}
Some(cliprdr::Union::format_list_response(data)) => {
Some(cliprdr::Union::FormatListResponse(data)) => {
Some(ClipbaordFile::ServerFormatListResponse {
conn_id: data.conn_id,
msg_flags: data.msg_flags,
})
}
Some(cliprdr::Union::format_data_request(data)) => {
Some(cliprdr::Union::FormatDataRequest(data)) => {
Some(ClipbaordFile::ServerFormatDataRequest {
conn_id: data.conn_id,
requested_format_id: data.requested_format_id,
})
}
Some(cliprdr::Union::format_data_response(data)) => {
Some(cliprdr::Union::FormatDataResponse(data)) => {
Some(ClipbaordFile::ServerFormatDataResponse {
conn_id: data.conn_id,
msg_flags: data.msg_flags,
format_data: data.format_data,
})
}
Some(cliprdr::Union::file_contents_request(data)) => {
Some(cliprdr::Union::FileContentsRequest(data)) => {
Some(ClipbaordFile::FileContentsRequest {
conn_id: data.conn_id,
stream_id: data.stream_id,
@@ -172,7 +172,7 @@ pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipbaordFile> {
clip_data_id: data.clip_data_id,
})
}
Some(cliprdr::Union::file_contents_response(data)) => {
Some(cliprdr::Union::FileContentsResponse(data)) => {
Some(ClipbaordFile::FileContentsResponse {
conn_id: data.conn_id,
msg_flags: data.msg_flags,

View File

@@ -8,11 +8,10 @@ use hbb_common::{
get_version_number, log,
message_proto::*,
protobuf::Message as _,
protobuf::ProtobufEnum,
protobuf::Enum,
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};
@@ -32,7 +31,7 @@ lazy_static::lazy_static! {
#[inline]
pub fn valid_for_numlock(evt: &KeyEvent) -> bool {
if let Some(key_event::Union::control_key(ck)) = evt.union {
if let Some(key_event::Union::ControlKey(ck)) = evt.union {
let v = ck.value();
(v >= ControlKey::Numpad0.value() && v <= ControlKey::Numpad9.value())
|| v == ControlKey::Decimal.value()
@@ -247,7 +246,7 @@ async fn test_nat_type_() -> ResultType<bool> {
return Ok(true);
}
let start = std::time::Instant::now();
let rendezvous_server = get_rendezvous_server(1_000).await;
let (rendezvous_server, _, _) = get_rendezvous_server(1_000).await;
let server1 = rendezvous_server;
let tmp: Vec<&str> = server1.split(":").collect();
if tmp.len() != 2 {
@@ -284,7 +283,7 @@ async fn test_nat_type_() -> ResultType<bool> {
socket.send(&msg_out).await?;
if let Some(Ok(bytes)) = socket.next_timeout(RENDEZVOUS_TIMEOUT).await {
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
if let Some(rendezvous_message::Union::test_nat_response(tnr)) = msg_in.union {
if let Some(rendezvous_message::Union::TestNatResponse(tnr)) = msg_in.union {
if i == 0 {
port1 = tnr.port;
} else {
@@ -316,31 +315,62 @@ async fn test_nat_type_() -> ResultType<bool> {
Ok(ok)
}
#[cfg(any(target_os = "android", target_os = "ios"))]
pub async fn get_rendezvous_server(_ms_timeout: u64) -> String {
Config::get_rendezvous_server()
pub async fn get_rendezvous_server(ms_timeout: u64) -> (String, Vec<String>, bool) {
#[cfg(any(target_os = "android", target_os = "ios"))]
let (mut a, mut b) = get_rendezvous_server_(ms_timeout);
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let (mut a, mut b) = get_rendezvous_server_(ms_timeout).await;
let mut b: Vec<String> = b
.drain(..)
.map(|x| {
if !x.contains(":") {
format!("{}:{}", x, config::RENDEZVOUS_PORT)
} else {
x
}
})
.collect();
let c = if b.contains(&a) {
b = b.drain(..).filter(|x| x != &a).collect();
true
} else {
a = b.pop().unwrap_or(a);
false
};
(a, b, c)
}
#[inline]
#[cfg(any(target_os = "android", target_os = "ios"))]
fn get_rendezvous_server_(_ms_timeout: u64) -> (String, Vec<String>) {
(
Config::get_rendezvous_server(),
Config::get_rendezvous_servers(),
)
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub async fn get_rendezvous_server(ms_timeout: u64) -> String {
async fn get_rendezvous_server_(ms_timeout: u64) -> (String, Vec<String>) {
crate::ipc::get_rendezvous_server(ms_timeout).await
}
#[inline]
#[cfg(any(target_os = "android", target_os = "ios"))]
pub async fn get_nat_type(_ms_timeout: u64) -> i32 {
Config::get_nat_type()
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
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();
hbb_common::config::ONLINE.lock().unwrap().clear();
Config::reset_online();
let mut futs = Vec::new();
for host in servers {
futs.push(tokio::spawn(async move {
@@ -363,11 +393,21 @@ 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_);
}
pub fn refresh_rendezvous_server() {
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
test_rendezvous_server();
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
std::thread::spawn(|| {
if crate::ipc::test_rendezvous_server().is_err() {
test_rendezvous_server();
}
});
}
#[inline]
pub fn get_time() -> i64 {
std::time::SystemTime::now()
@@ -412,7 +452,7 @@ pub const POSTFIX_SERVICE: &'static str = "_service";
#[inline]
pub fn is_control_key(evt: &KeyEvent, key: &ControlKey) -> bool {
if let Some(key_event::Union::control_key(ck)) = evt.union {
if let Some(key_event::Union::ControlKey(ck)) = evt.union {
ck.value() == key.value()
} else {
false
@@ -421,7 +461,7 @@ pub fn is_control_key(evt: &KeyEvent, key: &ControlKey) -> bool {
#[inline]
pub fn is_modifier(evt: &KeyEvent) -> bool {
if let Some(key_event::Union::control_key(ck)) = evt.union {
if let Some(key_event::Union::ControlKey(ck)) = evt.union {
let v = ck.value();
v == ControlKey::Alt.value()
|| v == ControlKey::Shift.value()
@@ -437,14 +477,15 @@ pub fn is_modifier(evt: &KeyEvent) -> bool {
}
pub fn check_software_update() {
std::thread::spawn(move || allow_err!(_check_software_update()));
std::thread::spawn(move || allow_err!(check_software_update_()));
}
#[tokio::main(flavor = "current_thread")]
async fn _check_software_update() -> hbb_common::ResultType<()> {
async fn check_software_update_() -> hbb_common::ResultType<()> {
sleep(3.).await;
let rendezvous_server = socket_client::get_target_addr(&get_rendezvous_server(1_000).await)?;
let rendezvous_server =
socket_client::get_target_addr(&format!("rs-sg.rustdesk.com:{}", config::RENDEZVOUS_PORT))?;
let mut socket =
socket_client::new_udp(Config::get_any_listen_addr(), RENDEZVOUS_TIMEOUT).await?;
@@ -457,7 +498,7 @@ async fn _check_software_update() -> hbb_common::ResultType<()> {
use hbb_common::protobuf::Message;
if let Some(Ok((bytes, _))) = socket.next_timeout(30_000).await {
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
if let Some(rendezvous_message::Union::software_update(su)) = msg_in.union {
if let Some(rendezvous_message::Union::SoftwareUpdate(su)) = msg_in.union {
let version = hbb_common::get_version_from_url(&su.url);
if get_version_number(&version) > get_version_number(crate::VERSION) {
*SOFTWARE_UPDATE_URL.lock().unwrap() = su.url;
@@ -496,14 +537,6 @@ pub fn is_setup(name: &str) -> bool {
name.to_lowercase().ends_with("setdown.exe") || name.to_lowercase().ends_with("安装.exe")
}
pub fn get_uuid() -> Vec<u8> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Ok(id) = machine_uid::get() {
return id.into();
}
Config::get_key_pair().1
}
pub fn get_custom_rendezvous_server(custom: String) -> String {
if !custom.is_empty() {
return custom;

View File

@@ -7,7 +7,9 @@ use hbb_common::{
config::{self, Config, Config2},
futures::StreamExt as _,
futures_util::sink::SinkExt,
log, timeout, tokio,
log,
password_security::password,
timeout, tokio,
tokio::io::{AsyncRead, AsyncWrite},
tokio_util::codec::Framed,
ResultType,
@@ -20,6 +22,16 @@ use std::{collections::HashMap, sync::atomic::Ordering};
#[cfg(not(windows))]
use std::{fs::File, io::prelude::*};
const STR_RANDOM_PASSWORD: &'static str = "random-password";
const STR_SECURITY_PASSWORD: &'static str = "security-password";
const STR_RANDOM_PASSWORD_UPDATE_METHOD: &'static str = "random-password-update-method";
const STR_RANDOM_PASSWORD_ENABLED: &'static str = "random-password-enabled";
const STR_SECURITY_PASSWORD_ENABLED: &'static str = "security-password-enabled";
const STR_ONETIME_PASSWORD_ENABLED: &'static str = "onetime-password-enabled";
const STR_ONETIME_PASSWORD_ACTIVATED: &'static str = "onetime-password-activated";
const STR_RANDOM_PASSWORD_VALID: &'static str = "random-password-valid";
pub const STR_PASSWORD_DESCRIPTION: &'static str = "password-description";
// State with timestamp, because std::time::Instant cannot be serialized
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
#[serde(tag = "t", content = "c")]
@@ -84,6 +96,45 @@ pub enum FS {
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")]
pub enum DataKeyboard {
Sequence(String),
KeyDown(enigo::Key),
KeyUp(enigo::Key),
KeyClick(enigo::Key),
GetKeyState(enigo::Key),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")]
pub enum DataKeyboardResponse {
GetKeyState(bool),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")]
pub enum DataMouse {
MoveTo(i32, i32),
MoveRelative(i32, i32),
Down(enigo::MouseButton),
Up(enigo::MouseButton),
Click(enigo::MouseButton),
ScrollX(i32),
ScrollY(i32),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")]
pub enum DataControl {
Resolution {
minx: i32,
maxx: i32,
miny: i32,
maxy: i32,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")]
pub enum Data {
@@ -127,6 +178,13 @@ pub enum Data {
ClipbaordFile(ClipbaordFile),
ClipboardFileEnabled(bool),
PrivacyModeState((i32, PrivacyModeState)),
TestRendezvousServer,
Bool((String, Option<bool>)),
Keyboard(DataKeyboard),
KeyboardResponse(DataKeyboardResponse),
Mouse(DataMouse),
Control(DataControl),
Empty,
}
#[tokio::main(flavor = "current_thread")]
@@ -281,12 +339,28 @@ async fn handle(data: Data, stream: &mut Connection) {
let value;
if name == "id" {
value = Some(Config::get_id());
} else if name == "password" {
value = Some(Config::get_password());
} else if name == STR_RANDOM_PASSWORD {
value = Some(password::random_password());
} else if name == STR_SECURITY_PASSWORD {
value = Some(Config::get_security_password());
} else if name == STR_RANDOM_PASSWORD_UPDATE_METHOD {
value = Some(password::update_method().to_string());
} else if name == STR_PASSWORD_DESCRIPTION {
value = Some(
password::random_password()
+ &password::security_enabled().to_string()
+ &password::random_enabled().to_string()
+ &password::onetime_password_enabled().to_string()
+ &password::onetime_password_activated().to_string(),
);
} else if name == "salt" {
value = Some(Config::get_salt());
} else if name == "rendezvous_server" {
value = Some(Config::get_rendezvous_server());
value = Some(format!(
"{},{}",
Config::get_rendezvous_server(),
Config::get_rendezvous_servers().join(",")
));
} else if name == "rendezvous_servers" {
value = Some(Config::get_rendezvous_servers().join(","));
} else {
@@ -298,8 +372,12 @@ async fn handle(data: Data, stream: &mut Connection) {
if name == "id" {
Config::set_key_confirmed(false);
Config::set_id(&value);
} else if name == "password" {
Config::set_password(&value);
} else if name == STR_RANDOM_PASSWORD {
password::set_random_password(&value);
} else if name == STR_SECURITY_PASSWORD {
Config::set_security_password(&value);
} else if name == STR_RANDOM_PASSWORD_UPDATE_METHOD {
password::set_update_method(&value);
} else if name == "salt" {
Config::set_salt(&value);
} else {
@@ -336,7 +414,39 @@ async fn handle(data: Data, stream: &mut Connection) {
.await
);
}
Data::TestRendezvousServer => {
crate::test_rendezvous_server();
}
Data::Bool((name, value)) => match value {
None => {
let value;
if name == STR_SECURITY_PASSWORD_ENABLED {
value = Some(password::security_enabled());
} else if name == STR_RANDOM_PASSWORD_ENABLED {
value = Some(password::random_enabled());
} else if name == STR_ONETIME_PASSWORD_ENABLED {
value = Some(password::onetime_password_enabled());
} else if name == STR_ONETIME_PASSWORD_ACTIVATED {
value = Some(password::onetime_password_activated());
} else if name == STR_RANDOM_PASSWORD_VALID {
value = Some(password::random_password_valid());
} else {
return;
}
allow_err!(stream.send(&Data::Bool((name, value))).await);
}
Some(value) => {
if name == STR_SECURITY_PASSWORD_ENABLED {
password::set_security_enabled(value);
} else if name == STR_RANDOM_PASSWORD_ENABLED {
password::set_random_enabled(value);
} else if name == STR_ONETIME_PASSWORD_ENABLED {
password::set_onetime_password_enabled(value);
} else if name == STR_ONETIME_PASSWORD_ACTIVATED {
password::set_onetime_password_activated(value);
}
}
},
_ => {}
}
}
@@ -419,6 +529,10 @@ where
.await
}
async fn send_bool(&mut self, name: &str, value: bool) -> ResultType<()> {
self.send(&Data::Bool((name.to_owned(), Some(value)))).await
}
pub async fn next_timeout(&mut self, ms_timeout: u64) -> ResultType<Option<Data>> {
Ok(timeout(ms_timeout, self.next()).await??)
}
@@ -490,9 +604,128 @@ pub async fn set_config(name: &str, value: String) -> ResultType<()> {
set_config_async(name, value).await
}
pub fn set_password(v: String) -> ResultType<()> {
Config::set_password(&v);
set_config("password", v)
#[tokio::main(flavor = "current_thread")]
async fn get_bool(name: &str) -> ResultType<Option<bool>> {
get_bool_async(name, 1_000).await
}
async fn get_bool_async(name: &str, ms_timeout: u64) -> ResultType<Option<bool>> {
let mut c = connect(ms_timeout, "").await?;
c.send(&Data::Bool((name.to_owned(), None))).await?;
if let Some(Data::Bool((name2, value))) = c.next_timeout(ms_timeout).await? {
if name == name2 {
return Ok(value);
}
}
return Ok(None);
}
pub async fn set_bool_async(name: &str, value: bool) -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send_bool(name, value).await?;
Ok(())
}
#[tokio::main(flavor = "current_thread")]
pub async fn set_bool(name: &str, value: bool) -> ResultType<()> {
set_bool_async(name, value).await
}
pub fn get_random_password() -> String {
if let Ok(Some(password)) = get_config(STR_RANDOM_PASSWORD) {
password::set_random_password(&password);
password
} else {
password::random_password()
}
}
pub fn set_random_password(v: String) -> ResultType<()> {
password::set_random_password(&v);
set_config(STR_RANDOM_PASSWORD, v)
}
pub fn set_security_password(v: String) -> ResultType<()> {
Config::set_security_password(&v);
set_config(STR_SECURITY_PASSWORD, v)
}
pub fn random_password_update_method() -> String {
if let Ok(Some(method)) = get_config(STR_RANDOM_PASSWORD_UPDATE_METHOD) {
password::set_update_method(&method);
method
} else {
password::update_method()
}
}
pub fn set_random_password_update_method(method: String) -> ResultType<()> {
password::set_update_method(&method);
set_config(STR_RANDOM_PASSWORD_UPDATE_METHOD, method)
}
pub fn is_random_password_enabled() -> bool {
if let Ok(Some(enabled)) = get_bool(STR_RANDOM_PASSWORD_ENABLED) {
password::set_random_enabled(enabled);
enabled
} else {
password::random_enabled()
}
}
pub fn set_random_password_enabled(enabled: bool) -> ResultType<()> {
password::set_random_enabled(enabled);
set_bool(STR_RANDOM_PASSWORD_ENABLED, enabled)
}
pub fn is_security_password_enabled() -> bool {
if let Ok(Some(enabled)) = get_bool(STR_SECURITY_PASSWORD_ENABLED) {
password::set_security_enabled(enabled);
enabled
} else {
password::security_enabled()
}
}
pub fn set_security_password_enabled(enabled: bool) -> ResultType<()> {
password::set_security_enabled(enabled);
set_bool(STR_SECURITY_PASSWORD_ENABLED, enabled)
}
pub fn is_onetime_password_enabled() -> bool {
if let Ok(Some(enabled)) = get_bool(STR_ONETIME_PASSWORD_ENABLED) {
password::set_onetime_password_enabled(enabled);
enabled
} else {
password::onetime_password_enabled()
}
}
pub fn set_onetime_password_enabled(enabled: bool) -> ResultType<()> {
password::set_onetime_password_enabled(enabled);
set_bool(STR_ONETIME_PASSWORD_ENABLED, enabled)
}
pub fn is_onetime_password_activated() -> bool {
if let Ok(Some(activated)) = get_bool(STR_ONETIME_PASSWORD_ACTIVATED) {
password::set_onetime_password_activated(activated);
activated
} else {
password::onetime_password_activated()
}
}
pub fn set_onetime_password_activated(activated: bool) -> ResultType<()> {
password::set_onetime_password_activated(activated);
set_bool(STR_ONETIME_PASSWORD_ACTIVATED, activated)
}
pub fn is_random_password_valid() -> bool {
if let Ok(Some(valid)) = get_bool(STR_RANDOM_PASSWORD_VALID) {
valid
} else {
password::random_password_valid()
}
}
pub fn get_id() -> String {
@@ -511,20 +744,17 @@ pub fn get_id() -> String {
}
}
pub fn get_password() -> String {
if let Ok(Some(v)) = get_config("password") {
Config::set_password(&v);
v
} else {
Config::get_password()
}
}
pub async fn get_rendezvous_server(ms_timeout: u64) -> String {
pub async fn get_rendezvous_server(ms_timeout: u64) -> (String, Vec<String>) {
if let Ok(Some(v)) = get_config_async("rendezvous_server", ms_timeout).await {
v
let mut urls = v.split(",");
let a = urls.next().unwrap_or_default().to_owned();
let b: Vec<String> = urls.map(|x| x.to_owned()).collect();
(a, b)
} else {
Config::get_rendezvous_server()
(
Config::get_rendezvous_server(),
Config::get_rendezvous_servers(),
)
}
}
@@ -636,3 +866,10 @@ pub async fn set_socks(value: config::Socks5Server) -> ResultType<()> {
.await?;
Ok(())
}
#[tokio::main(flavor = "current_thread")]
pub async fn test_rendezvous_server() -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send(&Data::TestRendezvousServer).await?;
Ok(())
}

291
src/lan.rs Normal file
View File

@@ -0,0 +1,291 @@
use hbb_common::{
allow_err,
anyhow::bail,
config::{self, Config, RENDEZVOUS_PORT},
log,
protobuf::Message as _,
rendezvous_proto::*,
tokio::{
self,
sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
},
ResultType,
};
use std::{
collections::{HashMap, HashSet},
net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs, UdpSocket},
time::Instant,
};
type Message = RendezvousMessage;
pub(super) fn start_listening() -> 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" {
if let Some(self_addr) = get_ipaddr_by_peer(&addr) {
let mut msg_out = Message::new();
let peer = PeerDiscovery {
cmd: "pong".to_owned(),
mac: get_mac(&self_addr),
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 discover() -> ResultType<()> {
let sockets = send_query()?;
let rx = spawn_wait_responses(sockets);
handle_received_peers(rx).await?;
log::info!("discover ping done");
Ok(())
}
pub fn send_wol(id: String) {
let interfaces = default_net::get_interfaces();
for peer in &config::LanPeers::load().peers {
if peer.id == id {
for (ip, mac) in peer.ip_mac.iter() {
if let Ok(mac_addr) = mac.parse() {
if let Ok(IpAddr::V4(ip)) = ip.parse() {
for interface in &interfaces {
for ipv4 in &interface.ipv4 {
if (u32::from(ipv4.addr) & u32::from(ipv4.netmask))
== (u32::from(ip) & u32::from(ipv4.netmask))
{
allow_err!(wol::send_wol(
mac_addr,
None,
Some(IpAddr::V4(ipv4.addr))
));
}
}
}
}
}
}
break;
}
}
}
#[inline]
fn get_broadcast_port() -> u16 {
(RENDEZVOUS_PORT + 3) as _
}
fn get_mac(ip: &IpAddr) -> String {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Ok(mac) = get_mac_by_ip(ip) {
mac.to_string()
} else {
"".to_owned()
}
#[cfg(any(target_os = "android", target_os = "ios"))]
"".to_owned()
}
fn get_all_ipv4s() -> ResultType<Vec<Ipv4Addr>> {
let mut ipv4s = Vec::new();
for interface in default_net::get_interfaces() {
for ipv4 in &interface.ipv4 {
ipv4s.push(ipv4.addr.clone());
}
}
Ok(ipv4s)
}
fn get_mac_by_ip(ip: &IpAddr) -> ResultType<String> {
for interface in default_net::get_interfaces() {
match ip {
IpAddr::V4(local_ipv4) => {
if interface.ipv4.iter().any(|x| x.addr == *local_ipv4) {
if let Some(mac_addr) = interface.mac_addr {
return Ok(mac_addr.address());
}
}
}
IpAddr::V6(local_ipv6) => {
if interface.ipv6.iter().any(|x| x.addr == *local_ipv6) {
if let Some(mac_addr) = interface.mac_addr {
return Ok(mac_addr.address());
}
}
}
}
}
bail!("No interface found for ip: {:?}", ip);
}
// Mainly from https://github.com/shellrow/default-net/blob/cf7ca24e7e6e8e566ed32346c9cfddab3f47e2d6/src/interface/shared.rs#L4
fn get_ipaddr_by_peer<A: ToSocketAddrs>(peer: A) -> Option<IpAddr> {
let socket = match UdpSocket::bind("0.0.0.0:0") {
Ok(s) => s,
Err(_) => return None,
};
match socket.connect(peer) {
Ok(()) => (),
Err(_) => return None,
};
match socket.local_addr() {
Ok(addr) => return Some(addr.ip()),
Err(_) => return None,
};
}
fn create_broadcast_sockets() -> ResultType<Vec<UdpSocket>> {
let mut sockets = Vec::new();
for v4_addr in get_all_ipv4s()? {
if v4_addr.is_private() {
let s = UdpSocket::bind(SocketAddr::from((v4_addr, 0)))?;
s.set_broadcast(true)?;
log::debug!("Bind socket to {}", &v4_addr);
sockets.push(s)
}
}
Ok(sockets)
}
fn send_query() -> ResultType<Vec<UdpSocket>> {
let sockets = create_broadcast_sockets()?;
if sockets.is_empty() {
bail!("Found no ipv4 addresses");
}
let mut msg_out = Message::new();
let peer = PeerDiscovery {
cmd: "ping".to_owned(),
..Default::default()
};
msg_out.set_peer_discovery(peer);
let maddr = SocketAddr::from(([255, 255, 255, 255], get_broadcast_port()));
for socket in &sockets {
socket.send_to(&msg_out.write_to_bytes()?, maddr)?;
}
log::info!("discover ping sent");
Ok(sockets)
}
fn wait_response(
socket: UdpSocket,
timeout: Option<std::time::Duration>,
tx: UnboundedSender<config::DiscoveryPeer>,
) -> ResultType<()> {
let mut last_recv_time = Instant::now();
socket.set_read_timeout(timeout)?;
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)) => {
last_recv_time = Instant::now();
if p.cmd == "pong" {
let mac = if let Some(self_addr) = get_ipaddr_by_peer(&addr) {
get_mac(&self_addr)
} else {
"".to_owned()
};
if mac != p.mac {
allow_err!(tx.send(config::DiscoveryPeer {
id: p.id.clone(),
ip_mac: HashMap::from([
(addr.ip().to_string(), p.mac.clone(),)
]),
username: p.username.clone(),
hostname: p.hostname.clone(),
platform: p.platform.clone(),
online: true,
}));
}
}
}
_ => {}
}
}
}
if last_recv_time.elapsed().as_millis() > 3_000 {
break;
}
}
Ok(())
}
fn spawn_wait_responses(sockets: Vec<UdpSocket>) -> UnboundedReceiver<config::DiscoveryPeer> {
let (tx, rx) = unbounded_channel::<_>();
for socket in sockets {
let tx_clone = tx.clone();
std::thread::spawn(move || {
allow_err!(wait_response(
socket,
Some(std::time::Duration::from_millis(10)),
tx_clone
));
});
}
rx
}
async fn handle_received_peers(mut rx: UnboundedReceiver<config::DiscoveryPeer>) -> ResultType<()> {
let mut peers = config::LanPeers::load().peers;
peers.iter_mut().for_each(|peer| {
peer.online = false;
});
let mut response_set = HashSet::new();
let mut last_write_time = Instant::now() - std::time::Duration::from_secs(4);
loop {
tokio::select! {
data = rx.recv() => match data {
Some(mut peer) => {
let in_response_set = !response_set.insert(peer.id.clone());
if let Some(pos) = peers.iter().position(|x| x.is_same_peer(&peer) ) {
let peer1 = peers.remove(pos);
if in_response_set {
peer.ip_mac.extend(peer1.ip_mac);
peer.online = true;
}
}
peers.insert(0, peer);
if last_write_time.elapsed().as_millis() > 300 {
config::LanPeers::store(&peers);
last_write_time = Instant::now();
}
}
None => {
break
}
}
}
}
config::LanPeers::store(&peers);
Ok(())
}

View File

@@ -8,6 +8,7 @@ mod de;
mod en;
mod eo;
mod es;
mod hu;
mod fr;
mod id;
mod it;
@@ -28,6 +29,7 @@ lazy_static::lazy_static! {
("tw", "繁體中文"),
("pt", "Português"),
("es", "Español"),
("hu", "Magyar"),
("ru", "Русский"),
("sk", "Slovenčina"),
("id", "Indonesia"),
@@ -68,6 +70,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
"tw" => tw::T.deref(),
"de" => de::T.deref(),
"es" => es::T.deref(),
"hu" => hu::T.deref(),
"ru" => ru::T.deref(),
"eo" => eo::T.deref(),
"id" => id::T.deref(),

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "进入隐私模式"),
("Out privacy mode", "退出隐私模式"),
("Language", "语言"),
("Keep RustDesk background service", "保持RustDesk后台服务"),
("Ignore Battery Optimizations", "忽略电池优化"),
("android_open_battery_optimizations_tip", "如需关闭此功能请在接下来的RustDesk应用设置页面中找到并进入 [电源] 页面,取消勾选 [不受限制]"),
("Random Password After Session", "会话结束更新随机密码"),
("Keep", "保持"),
("Update", "更新"),
("Disable", "禁用"),
("Onetime Password", "一次性口令"),
("Verification Method", "密码验证方式"),
("Enable security password", "启用安全密码"),
("Enable random password", "启用随机密码"),
("Enable onetime password", "启用一次性访问功能"),
("Disable onetime password", "禁用一次性访问功能"),
("Activate onetime password", "激活一次性访问功能"),
("Set security password", "设置安全密码"),
("Connection not allowed", "对方不允许连接"),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "v režimu soukromí"),
("Out privacy mode", "mimo režim soukromí"),
("Language", ""),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "I databeskyttelsestilstand"),
("Out privacy mode", "Databeskyttelsestilstand fra"),
("Language", ""),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "im Datenschutzmodus"),
("Out privacy mode", "Datenschutzmodus aus"),
("Language", "Sprache"),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -27,5 +27,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
("server_not_support", "Not yet supported by the server"),
("android_open_battery_optimizations_tip", "If you want to disable this feature, please go to the next RustDesk application settings page, find and enter [Battery], Uncheck [Unrestricted]"),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", ""),
("Out privacy mode", ""),
("Language", ""),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "En modo de privacidad"),
("Out privacy mode", "Fuera del modo de privacidad"),
("Language", ""),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "en mode privé"),
("Out privacy mode", "hors mode de confidentialité"),
("Language", "Langue"),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

304
src/lang/hu.rs Normal file
View File

@@ -0,0 +1,304 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Státusz"),
("Your Desktop", "A te asztalod"),
("desk_tip", "Az asztalod ezzel az ID-vel, és jelszóval érhető el."),
("Password", "Jelszó"),
("Ready", "Kész"),
("Established", "Létrejött"),
("connecting_status", "Kapcsolódás a RustDesk hálózatához..."),
("Enable Service", "A szolgáltatás bekapcsolása"),
("Start Service", "Szolgáltatás Elindítása"),
("Service is running", "A szolgáltatás fut"),
("Service is not running", "A szolgáltatás nem fut"),
("not_ready_status", "A RustDesk nem áll készen. Kérlek nézd meg a hálózati beállításaidat."),
("Control Remote Desktop", "Távoli Asztal Kontrollálása"),
("Transfer File", "Fájl Transzfer"),
("Connect", "Kapcsolódás"),
("Recent Sessions", "Korábbi Sessionök"),
("Address Book", "Címköny"),
("Confirmation", "Megerősít"),
("TCP Tunneling", "TCP Tunneling"),
("Remove", "Eltávolít"),
("Refresh random password", "Véletlenszerű jelszó frissítése"),
("Set your own password", "Saját jelszó beállítása"),
("Enable Keyboard/Mouse", "Billentyűzet/Egér bekapcsolása"),
("Enable Clipboard", "Megosztott vágólap bekapcsolása"),
("Enable File Transfer", "Fájl transzer bekapcsolása"),
("Enable TCP Tunneling", "TCP Tunneling bekapcsolása"),
("IP Whitelisting", "IP Fehérlista"),
("ID/Relay Server", "ID/Relay Szerver"),
("Stop service", "Szolgáltatás Kikapcsolása"),
("Change ID", "ID Megváltoztatása"),
("Website", "Weboldal"),
("About", "Rólunk: "),
("Mute", "Némítás"),
("Audio Input", "Audo Bemenet"),
("Enhancements", "Javítások"),
("Hardware Codec", "Hardware Kodek"),
("Adaptive Bitrate", "Adaptív Bitrate"),
("ID Server", "ID Szerver"),
("Relay Server", "Relay Szerver"),
("API Server", "API Szerver"),
("invalid_http", "A címnek mindenképpen http(s)://-el kell kezdődnie."),
("Invalid IP", "A megadott íp cím helytelen."),
("id_change_tip", "Csak a-z, A-Z, 0-9 csoportokba tartozó karakterek, illetve a _ karakter van engedélyezve. Az első karakternek mindenképpen a-z, A-Z csoportokba kell esnie. Az ID hosszúsága 6-tól, 16 karakter."),
("Invalid format", "Érvénytelen formátum"),
("server_not_support", "Még nem támogatott a szerver által"),
("Not available", "Nem érhető el"),
("Too frequent", "Túl gyakori"),
("Cancel", "Mégsem"),
("Skip", "Kihagy"),
("Close", "Bezár"),
("Retry", "Újrapróbálkozás"),
("OK", "OK"),
("Password Required", "A jelszó megadása kötelező"),
("Please enter your password", "Kérlek írd be a jelszavad"),
("Remember password", "Kérlek emlékezz a jelszóra"),
("Wrong Password", "Hibás jelszó"),
("Do you want to enter again?", "Újra szeretnéd próbálni?"),
("Connection Error", "Kapcsolódási Hiba"),
("Error", "Hiba"),
("Reset by the peer", "A kapcsolatot alaphelyzetbe állt"),
("Connecting...", "Kapcsolódás..."),
("Connection in progress. Please wait.", "A kapcsolódás folyamatban van. Kérlek várj."),
("Please try 1 minute later", "Kérlek próbáld újra 1 perc múlva."),
("Login Error", "Belépési Hiba"),
("Successful", "Sikeres"),
("Connected, waiting for image...", "Kapcsolódva, várakozás a képre..."),
("Name", "Név"),
("Type", "Fajta"),
("Modified", "Módosított"),
("Size", "Méret"),
("Show Hidden Files", "Rejtett Fájlok Mutatása"),
("Receive", "Kapni"),
("Send", "Küldeni"),
("Refresh File", "Fájlok Frissítése"),
("Local", "Lokális"),
("Remote", "Távoli"),
("Remote Computer", "Távoli Számítógép"),
("Local Computer", "Lokális Számítógép"),
("Confirm Delete", "Törlés Megerősítése"),
("Delete", "Törlés"),
("Properties", "Tulajdonságok"),
("Multi Select", "Több fájl kiválasztása"),
("Empty Directory", "Üres Könyvtár"),
("Not an empty directory", "Nem egy üres könyvtár"),
("Are you sure you want to delete this file?", "Biztosan törölni szeretnéd ezt a fájlt?"),
("Are you sure you want to delete this empty directory?", "Biztosan törölni szeretnéd ezt az üres könyvtárat?"),
("Are you sure you want to delete the file of this directory?", "Biztosan törölni szeretnéd a fájlokat ebben a könyvtárban?"),
("Do this for all conflicts", "Ezt tedd az összes konfliktussal"),
("This is irreversible!", "Ez a folyamat visszafordíthatatlan!"),
("Deleting", "A törlés folyamatban"),
("files", "fájlok"),
("Waiting", "Várunk"),
("Finished", "Végzett"),
("Speed", "Gyorsaság"),
("Custom Image Quality", "Egyedi Képminőség"),
("Privacy mode", "Inkognító mód"),
("Block user input", "Felhasználói input blokkokolása"),
("Unblock user input", "Felhasználói input blokkolásának feloldása"),
("Adjust Window", "Ablakméret beállítása"),
("Original", "Eredeti"),
("Shrink", "Zsugorított"),
("Stretch", "Nyújtott"),
("Good image quality", "Jó képminőség"),
("Balanced", "Balanszolt"),
("Optimize reaction time", "Válaszidő optimializálása"),
("Custom", "Egyedi"),
("Show remote cursor", "Távoli kurzor mutatása"),
("Show quality monitor", "Minőségi monitor mutatása"),
("Disable clipboard", "Vágólap Kikapcsolása"),
("Lock after session end", "Lezárás a session végén"),
("Insert", "Beszúrás"),
("Insert Lock", "Beszúrási Zároló"),
("Refresh", "Frissítés"),
("ID does not exist", "Ez az ID nem létezik"),
("Failed to connect to rendezvous server", "A randevú szerverhez való kapcsolódás sikertelen"),
("Please try later", "Kérlek próbád később"),
("Remote desktop is offline", "A távoli asztal offline"),
("Key mismatch", "Eltérés a kulcsokban"),
("Timeout", "Időtúllépés"),
("Failed to connect to relay server", "A relay szerverhez való kapcsolódás sikertelen"),
("Failed to connect via rendezvous server", "A randevú szerverrel való kapcsolódás sikertelen"),
("Failed to connect via relay server", "A relay szerverrel való kapcsolódás sikertelen"),
("Failed to make direct connection to remote desktop", "A távoli asztalhoz való direkt kapcsolódás sikertelen"),
("Set Password", "Jelszó Beállítása"),
("OS Password", "Operációs Rendszer Jelszavának Beállítása"),
("install_tip", "Az UAC (Felhasználói Fiók Felügyelet) miatt, a RustDesk nem fog rendesen funkcionálni mint távoli oldal néhány esetben. Hogy ezt kikerüld, vagy kikapcsold, kérlek nyomj rá a gombra ezalatt az üzenet alatt, hogy feltelepítsd a RustDesket a rendszerre."),
("Click to upgrade", "Kattints a frissítés telepítéséhez"),
("Click to download", "Kattints a letöltéshez"),
("Click to update", "Kattints a frissítés letöltéséhez"),
("Configure", "Beállítás"),
("config_acc", "Ahhoz hogy a RustDesket távolról irányítani tudd, \"Elérhetőségi\" jogokat kell adnod a RustDesk-nek."),
("config_screen", "Ahhoz hogy a RustDesket távolról irányítani tudd, \"Képernyőfelvételi\" jogokat kell adnod a RustDesk-nek."),
("Installing ...", "Telepítés..."),
("Install", "Telepítés"),
("Installation", "Telepítés"),
("Installation Path", "Telepítési útvonal"),
("Create start menu shortcuts", "Start menu parancsikon létrehozása"),
("Create desktop icon", "Asztali icon létrehozása"),
("agreement_tip", "Azzal hogy elindítod a telepítést, elfogadod a licenszszerződést."),
("Accept and Install", "Elfogadás és Telepítés"),
("End-user license agreement", "Felhasználói licencszerződés"),
("Generating ...", "Generálás..."),
("Your installation is lower version.", "A jelenleg feltelepített verzió régebbi."),
("not_close_tcp_tip", "Ne zárd be ezt az ablakot miközben a tunnelt használod"),
("Listening ...", "Halgazózás..."),
("Remote Host", "Távoli Host"),
("Remote Port", "Távoli Port"),
("Action", "Akció"),
("Add", "Add"),
("Local Port", "Lokális Port"),
("setup_server_tip", "Egy gyorsabb kapcsolatért, kérlek hostolj egy saját szervert"),
("Too short, at least 6 characters.", "Túl rövid, legalább 6 karakter"),
("The confirmation is not identical.", "A megerősítés nem volt azonos"),
("Permissions", "Jogok"),
("Accept", "Elfogad"),
("Dismiss", "Elutasít"),
("Disconnect", "Szétkapcsolás"),
("Allow using keyboard and mouse", "Billentyűzet és egér használatának engedélyezése"),
("Allow using clipboard", "Vágólap használatának engedélyezése"),
("Allow hearing sound", "Hang átvitelének engedélyezése"),
("Allow file copy and paste", "Fájlok másolásának és beillesztésének engedélyezése"),
("Connected", "Kapcsolódva"),
("Direct and encrypted connection", "Direkt, és titkosított kapcsolat"),
("Relayed and encrypted connection", "Relayelt, és titkosított kapcsolat"),
("Direct and unencrypted connection", "Direkt, és nem titkosított kapcsolat"),
("Relayed and unencrypted connection", "Rekayelt, és nem titkosított kapcsolat"),
("Enter Remote ID", "Kérlek írd be a távoli ID-t"),
("Enter your password", "Kérlek írd be a jelszavadat"),
("Logging in...", "A belépés folyamatban..."),
("Enable RDP session sharing", "Az RDP session megosztás engedélyezése"),
("Auto Login", "Automatikus Login"),
("Enable Direct IP Access", "Direkt IP elérés engedélyezése"),
("Rename", "Átnevezés"),
("Space", "Hely"),
("Create Desktop Shortcut", "Asztali Parancsikon Lértehozása"),
("Change Path", "Útvonal Megváltoztatása"),
("Create Folder", "Mappa Készítése"),
("Please enter the folder name", "Kérlek írd be a mappa nevét"),
("Fix it", "Kérlek javísd meg"),
("Warning", "Figyelem"),
("Login screen using Wayland is not supported", "A belépési kijelzővel a Wayland használata nem támogatott"),
("Reboot required", "Újraindítás szükséges"),
("Unsupported display server ", "Nem támogatott kijelző szerver"),
("x11 expected", "x11-re számítottt"),
("Port", "Port"),
("Settings", "Beállítások"),
("Username", "Felhasználónév"),
("Invalid port", "Érvénytelen port"),
("Closed manually by the peer", "A kapcsolat manuálisan be lett zárva a másik fél álltal"),
("Enable remote configuration modification", "Távoli konfiguráció módosítás engedélyezése"),
("Run without install", "Futtatás feltelepítés nélkül"),
("Always connected via relay", "Mindig relay által kapcsolódott"),
("Always connect via relay", "Mindig relay által kapcsolódik"),
("whitelist_tip", "Csak a fehérlistán lévő címek érhetnek el"),
("Login", "Belépés"),
("Logout", "Kilépés"),
("Tags", "Tagok"),
("Search ID", "ID keresés"),
("Current Wayland display server is not supported", "Jelenleg a Wayland display szerver nem támogatott"),
("whitelist_sep", "Ide jönnek a címek, vesző, pontosvessző, space, vagy új sorral elválasztva"),
("Add ID", "ID Hozzáadása"),
("Add Tag", "Tag Hozzáadása"),
("Unselect all tags", "Az összes tag kiválasztásának törlése"),
("Network error", "Hálózati hiba"),
("Username missed", "A felhasználónév kimaradt"),
("Password missed", "A jelszó kimaradt"),
("Wrong credentials", "Hibás felhasználónév vagy jelszó"),
("Edit Tag", "A tag(ok) szerkeztése"),
("Unremember Password", "A jelszó megjegyzésének törlése"),
("Favorites", "Kedvencek"),
("Add to Favorites", "Hozzáadás a kedvencekhez"),
("Remove from Favorites", "Eltávolítás a kedvencektől"),
("Empty", "Üres"),
("Invalid folder name", "Helytelen fájlnév"),
("Socks5 Proxy", "Socks5-ös Proxy"),
("Hostname", "Hostnév"),
("Discovered", "Felfedezés"),
("install_daemon_tip", "Ahhoz hogy a RustDesk bootkor elinduljon, telepítened kell a rendszer szolgáltatást."),
("Remote ID", "Távoli ID"),
("Paste", "Beillesztés"),
("Paste here?", "Beillesztés ide?"),
("Are you sure to close the connection?", "Biztos vagy benne hogy be szeretnéd zárni a kapcsolatot?"),
("Download new version", "Új verzó letöltése"),
("Touch mode", "Érintési mód bekapcsolása"),
("Mouse mode", "Egérhasználati mód bekapcsolása"),
("One-Finger Tap", "Egyújas érintés"),
("Left Mouse", "Baloldali Egér"),
("One-Long Tap", "Egy hosszú érintés"),
("Two-Finger Tap", "Két újas érintés"),
("Right Mouse", "Jobboldali Egér"),
("One-Finger Move", "Egyújas mozgatás"),
("Double Tap & Move", "Kétszeri érintés, és Mozgatás"),
("Mouse Drag", "Egérrel való húzás"),
("Three-Finger vertically", "Három ujj függőlegesen"),
("Mouse Wheel", "Egérgörgő"),
("Two-Finger Move", "Kátújas mozgatás"),
("Canvas Move", "Nézet Mozgatása"),
("Pinch to Zoom", "Húzd össze a nagyításhoz"),
("Canvas Zoom", "Nézet Nagyítása"),
("Reset canvas", "Nézet visszaállítása"),
("No permission of file transfer", "Nincs jogod fájl transzer indításához"),
("Note", "Megyjegyzés"),
("Connection", "Kapcsolat"),
("Share Screen", "Képernyőmegosztás"),
("CLOSE", "LETILT"),
("OPEN", "ENGEDÉLYEZ"),
("Chat", "Chat"),
("Total", "Összes"),
("items", "Tárgyak"),
("Selected", "Kiválasztott"),
("Screen Capture", "Képernyőrögzítés"),
("Input Control", "Input Kontrol"),
("Audio Capture", "Audió Rögzítés"),
("File Connection", "Fájlkapcsolat"),
("Screen Connection", "Új Vizuális Kapcsolat"),
("Do you accept?", "Elfogadod?"),
("Open System Setting", "Rendszer beállítások megnyitása"),
("How to get Android input permission?", "Hogyan állíthatok be Android input jogokat?"),
("android_input_permission_tip1", "Ahhoz hogy egy távoli eszköz kontolálhassa az Android eszközödet egérrel vagy érintéssel, jogot kell adnod a RustDesk-nek, hogy használja az \"Elérhetőségi\" szolgáltatást."),
("android_input_permission_tip2", "Kérlek navigálj a rendszer beállításaihoz, keresd meg vagy írd be hogy [Feltelepített Szolgáltatások], és kapcsold be a [RustDesk Input] szolgáltatást."),
("android_new_connection_tip", "Új kontrollálási kérés érkezett, amely irányítani szeretné az eszközöded."),
("android_service_will_start_tip", "A \"Képernyőrögzítés\" engedélyezése automatikusan elindítja majd a szolgáltatást, amely megengedi más eszközöknek hogy kérést kezdeményezzenek az eszköz felé."),
("android_stop_service_tip", "A szolgáltatás bezárása automatikusan szétkapcsol minden létező kapcsolatot."),
("android_version_audio_tip", "A jelenlegi Android verzió nem támogatja a hangrögzítést, kérlek frissíts legalább Android 10-re, vagy egy újabb verzióra."),
("android_start_service_tip", "Nyomj a [Szolgáltatás Indítása] opcióra, vagy adj [Képernyőrözítési] jogot az applikációnak hogy elindítsd a képernyőmegosztó szolgáltatást."),
("Account", "Fiók"),
("Overwrite", "Felülírás"),
("This file exists, skip or overwrite this file?", "Ez a fájl már létezik, skippeljünk, vagy felülírjuk ezt a fájlt?"),
("Quit", "Kilépés"),
("doc_mac_permission", "https://rustdesk.com/docs/hu/manual/mac/#enable-permissions"),
("Help", "Segítség"),
("Failed", "Sikertelen"),
("Succeeded", "Sikeres"),
("Someone turns on privacy mode, exit", "Valaki bekacsolta a privát módot, lépj ki"),
("Unsupported", "Nem támogatott"),
("Peer denied", "Elutasítva a távoli fél álltal"),
("Please install plugins", "Kérlek telepítsd a pluginokat"),
("Peer exit", "A távoli fél kilépett"),
("Failed to turn off", "Nem tudtuk kikapcsolni"),
("Turned off", "Kikapcsolva"),
("In privacy mode", "Belépés a privát módba"),
("Out privacy mode", "Kilépés a privát módból"),
("Language", "Nyelv"),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "Dalam mode privasi"),
("Out privacy mode", "Keluar dari mode privasi"),
("Language", ""),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "In modalità privacy"),
("Out privacy mode", "Fuori modalità privacy"),
("Language", "Linguaggio"),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "No modo de privacidade"),
("Out privacy mode", "Fora do modo de privacidade"),
("Language", ""),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -222,7 +222,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Remote ID", "Удаленный идентификатор"),
("Paste", "Вставить"),
("Paste here?", "Вставить сюда?"),
("Are you sure to close the connection?", "Вы уверены, что хотите закрыть соединение?"),
("Are you sure to close the connection?", "Вы уверены, что хотите завершить подключение?"),
("Download new version", "Скачать новую версию"),
("Touch mode", "Сенсорный режим"),
("Mouse mode", "Режим мыши"),
@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "В режиме конфиденциальности"),
("Out privacy mode", "Выход из режима конфиденциальности"),
("Language", "Язык"),
("Keep RustDesk background service", "Сохранить фоновый службу RustDesk"),
("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"),
("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек "),
("Random Password After Session", "Случайный пароль после сеанса"),
("Keep", "Оставить"),
("Update", "Обновить"),
("Disable", "Отключить"),
("Onetime Password", "Одноразовый пароль"),
("Verification Method", "Метод верификации"),
("Enable security password", "Включить пароль безопасности"),
("Enable random password", "Включить случайный пароль"),
("Enable onetime password", "Включить одноразовый пароль"),
("Disable onetime password", "Отключить одноразовый пароль"),
("Activate onetime password", "Активировать одноразовый пароль"),
("Set security password", "Задать пароль безопасности"),
("Connection not allowed", "Подключение не разрешено"),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "V režime súkromia"),
("Out privacy mode", "Mimo režimu súkromia"),
("Language", ""),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", ""),
("Out privacy mode", ""),
("Language", ""),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "Gizlilik modunda"),
("Out privacy mode", "Gizlilik modu dışında"),
("Language", ""),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
].iter().cloned().collect();
}

View File

@@ -284,5 +284,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("In privacy mode", "開啟隱私模式"),
("Out privacy mode", "退出隱私模式"),
("Language", "語言"),
("Keep RustDesk background service", "保持RustDesk後台服務"),
("Ignore Battery Optimizations", "忽略電池優化"),
("android_open_battery_optimizations_tip", "如需關閉此功能請在接下來的RustDesk應用設置頁面中找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
("Random Password After Session", "會話結束更新隨機密碼"),
("Keep", "保持"),
("Update", "更新"),
("Disable", "禁用"),
("Onetime Password", "一次性口令"),
("Verification Method", "密碼驗證方式"),
("Enable security password", "啟用安全密碼"),
("Enable random password", "啟用隨機密碼"),
("Enable onetime password", "啟用一次性訪問功能"),
("Disable onetime password", "禁用一次性訪問功能"),
("Activate onetime password", "激活一次性訪問功能"),
("Set security password", "設置安全密碼"),
("Connection not allowed", "對方不允許連接"),
].iter().cloned().collect();
}

View File

@@ -12,6 +12,8 @@ mod client;
#[cfg(not(any(target_os = "ios")))]
mod rendezvous_mediator;
#[cfg(not(any(target_os = "ios")))]
mod lan;
#[cfg(not(any(target_os = "ios")))]
pub use self::rendezvous_mediator::*;
/// cbindgen:ignore
pub mod common;

View File

@@ -108,6 +108,10 @@ fn main() {
args.len() > 1,
));
return;
} else if args[0] == "--extract" {
#[cfg(feature = "with_rc")]
hbb_common::allow_err!(crate::rc::extract_resources(&args[1]));
return;
}
}
if args[0] == "--remove" {
@@ -148,7 +152,7 @@ fn main() {
return;
} else if args[0] == "--password" {
if args.len() == 2 {
ipc::set_password(args[1].to_owned()).unwrap();
ipc::set_security_password(args[1].to_owned()).unwrap();
}
return;
} else if args[0] == "--check-hwcodec-config" {

View File

@@ -591,7 +591,6 @@ impl Connection {
log::debug!("Exit io_loop of id={}", session.id);
}
Err(err) => {
crate::common::test_rendezvous_server();
session.msgbox("error", "Connection Error", &err.to_string());
}
}
@@ -600,7 +599,7 @@ impl Connection {
async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool {
if let Ok(msg_in) = Message::parse_from_bytes(&data) {
match msg_in.union {
Some(message::Union::video_frame(vf)) => {
Some(message::Union::VideoFrame(vf)) => {
if !self.first_frame {
self.first_frame = true;
}
@@ -611,21 +610,21 @@ impl Connection {
s.add(ZeroCopyBuffer(self.video_handler.rgb.clone()));
}
}
Some(message::Union::hash(hash)) => {
Some(message::Union::Hash(hash)) => {
self.session.handle_hash(hash, peer).await;
}
Some(message::Union::login_response(lr)) => match lr.union {
Some(login_response::Union::error(err)) => {
Some(message::Union::LoginResponse(lr)) => match lr.union {
Some(login_response::Union::Error(err)) => {
if !self.session.handle_login_error(&err) {
return false;
}
}
Some(login_response::Union::peer_info(pi)) => {
Some(login_response::Union::PeerInfo(pi)) => {
self.session.handle_peer_info(pi);
}
_ => {}
},
Some(message::Union::clipboard(cb)) => {
Some(message::Union::Clipboard(cb)) => {
if !self.session.lc.read().unwrap().disable_clipboard {
let content = if cb.compress {
decompress(&cb.content)
@@ -638,7 +637,7 @@ impl Connection {
}
}
}
Some(message::Union::cursor_data(cd)) => {
Some(message::Union::CursorData(cd)) => {
let colors = hbb_common::compress::decompress(&cd.colors);
self.session.push_event(
"cursor_data",
@@ -655,18 +654,18 @@ impl Connection {
],
);
}
Some(message::Union::cursor_id(id)) => {
Some(message::Union::CursorId(id)) => {
self.session
.push_event("cursor_id", vec![("id", &id.to_string())]);
}
Some(message::Union::cursor_position(cp)) => {
Some(message::Union::CursorPosition(cp)) => {
self.session.push_event(
"cursor_position",
vec![("x", &cp.x.to_string()), ("y", &cp.y.to_string())],
);
}
Some(message::Union::file_response(fr)) => match fr.union {
Some(file_response::Union::dir(fd)) => {
Some(message::Union::FileResponse(fr)) => match fr.union {
Some(file_response::Union::Dir(fd)) => {
let mut entries = fd.entries.to_vec();
if self.session.peer_platform() == "Windows" {
fs::transform_windows_path(&mut entries);
@@ -680,7 +679,7 @@ impl Connection {
job.set_files(entries);
}
}
Some(file_response::Union::block(block)) => {
Some(file_response::Union::Block(block)) => {
if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) {
if let Err(_err) = job.write(block, None).await {
// to-do: add "skip" for writing job
@@ -688,17 +687,17 @@ impl Connection {
self.update_jobs_status();
}
}
Some(file_response::Union::done(d)) => {
Some(file_response::Union::Done(d)) => {
if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) {
job.modify_time();
fs::remove_job(d.id, &mut self.write_jobs);
}
self.handle_job_status(d.id, d.file_num, None);
}
Some(file_response::Union::error(e)) => {
Some(file_response::Union::Error(e)) => {
self.handle_job_status(e.id, e.file_num, Some(e.error));
}
Some(file_response::Union::digest(digest)) => {
Some(file_response::Union::Digest(digest)) => {
if digest.is_upload {
if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) {
if let Some(file) = job.files().get(digest.file_num as usize) {
@@ -709,9 +708,9 @@ impl Connection {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
file_transfer_send_confirm_request::Union::offset_blk(0)
file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else {
file_transfer_send_confirm_request::Union::skip(
file_transfer_send_confirm_request::Union::Skip(
true,
)
}),
@@ -741,7 +740,7 @@ impl Connection {
let msg= new_send_confirm(FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(file_transfer_send_confirm_request::Union::skip(true)),
union: Some(file_transfer_send_confirm_request::Union::Skip(true)),
..Default::default()
});
self.session.send_msg(msg);
@@ -753,9 +752,9 @@ impl Connection {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
file_transfer_send_confirm_request::Union::offset_blk(0)
file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else {
file_transfer_send_confirm_request::Union::skip(true)
file_transfer_send_confirm_request::Union::Skip(true)
}),
..Default::default()
},
@@ -775,7 +774,7 @@ impl Connection {
FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)),
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
..Default::default()
},
);
@@ -792,15 +791,15 @@ impl Connection {
}
_ => {}
},
Some(message::Union::misc(misc)) => match misc.union {
Some(misc::Union::audio_format(f)) => {
Some(message::Union::Misc(misc)) => match misc.union {
Some(misc::Union::AudioFormat(f)) => {
self.audio_handler.handle_format(f); //
}
Some(misc::Union::chat_message(c)) => {
Some(misc::Union::ChatMessage(c)) => {
self.session
.push_event("chat_client_mode", vec![("text", &c.text)]);
}
Some(misc::Union::permission_info(p)) => {
Some(misc::Union::PermissionInfo(p)) => {
log::info!("Change permission {:?} -> {}", p.permission, p.enabled);
use permission_info::Permission;
self.session.push_event(
@@ -816,7 +815,7 @@ impl Connection {
)],
);
}
Some(misc::Union::switch_display(s)) => {
Some(misc::Union::SwitchDisplay(s)) => {
self.video_handler.reset();
self.session.push_event(
"switch_display",
@@ -829,22 +828,22 @@ impl Connection {
],
);
}
Some(misc::Union::close_reason(c)) => {
Some(misc::Union::CloseReason(c)) => {
self.session.msgbox("error", "Connection Error", &c);
return false;
}
_ => {}
},
Some(message::Union::test_delay(t)) => {
Some(message::Union::TestDelay(t)) => {
self.session.handle_test_delay(t, peer).await;
}
Some(message::Union::audio_frame(frame)) => {
Some(message::Union::AudioFrame(frame)) => {
if !self.session.lc.read().unwrap().disable_audio {
self.audio_handler.handle_frame(frame);
}
}
Some(message::Union::file_action(action)) => match action.union {
Some(file_action::Union::send_confirm(c)) => {
Some(message::Union::FileAction(action)) => match action.union {
Some(file_action::Union::SendConfirm(c)) => {
if let Some(job) = fs::get_job(c.id, &mut self.read_jobs) {
job.confirm(&c);
}
@@ -1030,9 +1029,9 @@ impl Connection {
id,
file_num,
union: if need_override {
Some(file_transfer_send_confirm_request::Union::offset_blk(0))
Some(file_transfer_send_confirm_request::Union::OffsetBlk(0))
} else {
Some(file_transfer_send_confirm_request::Union::skip(true))
Some(file_transfer_send_confirm_request::Union::Skip(true))
},
..Default::default()
});
@@ -1048,9 +1047,9 @@ impl Connection {
id,
file_num,
union: if need_override {
Some(file_transfer_send_confirm_request::Union::offset_blk(0))
Some(file_transfer_send_confirm_request::Union::OffsetBlk(0))
} else {
Some(file_transfer_send_confirm_request::Union::skip(true))
Some(file_transfer_send_confirm_request::Union::Skip(true))
},
..Default::default()
});
@@ -1452,7 +1451,7 @@ pub mod connection_manager {
let mut req = FileTransferSendConfirmRequest {
id,
file_num,
union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)),
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
..Default::default()
};
let digest = FileTransferDigest {

View File

@@ -1,4 +1,5 @@
use super::{CursorData, ResultType};
pub use hbb_common::platform::linux::*;
use hbb_common::{allow_err, bail, log};
use libc::{c_char, c_int, c_void};
use std::{
@@ -8,6 +9,7 @@ use std::{
Arc,
},
};
type Xdo = *const c_void;
pub const PA_SAMPLE_RATE: u32 = 48000;
@@ -143,7 +145,75 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
}
}
fn start_uinput_service() {
use crate::server::uinput::service;
std::thread::spawn(|| {
service::start_service_control();
});
std::thread::spawn(|| {
service::start_service_keyboard();
});
std::thread::spawn(|| {
service::start_service_mouse();
});
}
fn try_start_user_service(username: &str) {
if username == "" || username == "root" {
return;
}
if let Ok(mut cur_username) =
run_cmds("ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1".to_owned())
{
cur_username = cur_username.trim().to_owned();
if cur_username != "root" && cur_username != username {
let _ = run_cmds(format!(
"systemctl --machine={}@.host --user stop rustdesk",
&cur_username
));
} else if cur_username == username {
return;
}
}
let _ = run_cmds(format!(
"systemctl --machine={}@.host --user start rustdesk",
username
));
}
fn try_stop_user_service() {
if let Ok(mut username) =
run_cmds("ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1".to_owned())
{
username = username.trim().to_owned();
if username != "root" {
let _ = run_cmds(format!(
"systemctl --machine={}@.host --user stop rustdesk",
&username
));
}
}
}
fn stop_server(server: &mut Option<std::process::Child>) {
if let Some(mut ps) = server.take() {
allow_err!(ps.kill());
std::thread::sleep(std::time::Duration::from_millis(30));
match ps.try_wait() {
Ok(Some(_status)) => {}
Ok(None) => {
let _res = ps.wait();
}
Err(e) => log::error!("error attempting to wait: {e}"),
}
}
}
pub fn start_os_service() {
start_uinput_service();
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
let mut uid = "".to_owned();
@@ -157,85 +227,106 @@ pub fn start_os_service() {
let mut cm0 = false;
let mut last_restart = std::time::Instant::now();
while running.load(Ordering::SeqCst) {
let cm = get_cm();
let tmp = get_active_userid();
let mut start_new = false;
if tmp != uid && !tmp.is_empty() {
uid = tmp;
log::info!("uid of seat0: {}", uid);
let gdm = format!("/run/user/{}/gdm/Xauthority", uid);
let mut auth = get_env_tries("XAUTHORITY", &uid, 10);
if auth.is_empty() {
auth = if std::path::Path::new(&gdm).exists() {
gdm
} else {
let username = get_active_username();
if username == "root" {
format!("/{}/.Xauthority", username)
let username = get_active_username();
let is_wayland = current_is_wayland();
if username == "root" || !is_wayland {
// try stop user service
try_stop_user_service();
// try start subprocess "--server"
let cm = get_cm();
let tmp = get_active_userid();
let mut start_new = false;
if tmp != uid && !tmp.is_empty() {
uid = tmp;
log::info!("uid of seat0: {}", uid);
let gdm = format!("/run/user/{}/gdm/Xauthority", uid);
let mut auth = get_env_tries("XAUTHORITY", &uid, 10);
if auth.is_empty() {
auth = if std::path::Path::new(&gdm).exists() {
gdm
} else {
let tmp = format!("/home/{}/.Xauthority", username);
if std::path::Path::new(&tmp).exists() {
tmp
let username = get_active_username();
if username == "root" {
format!("/{}/.Xauthority", username)
} else {
format!("/var/lib/{}/.Xauthority", username)
let tmp = format!("/home/{}/.Xauthority", username);
if std::path::Path::new(&tmp).exists() {
tmp
} else {
format!("/var/lib/{}/.Xauthority", username)
}
}
}
};
}
let mut d = get_env("DISPLAY", &uid);
if d.is_empty() {
d = get_display();
}
if d.is_empty() {
d = ":0".to_owned();
}
d = d.replace(&whoami::hostname(), "").replace("localhost", "");
log::info!("DISPLAY: {}", d);
log::info!("XAUTHORITY: {}", auth);
std::env::set_var("XAUTHORITY", auth);
std::env::set_var("DISPLAY", d);
if let Some(ps) = server.as_mut() {
allow_err!(ps.kill());
std::thread::sleep(std::time::Duration::from_millis(30));
last_restart = std::time::Instant::now();
}
} else if !cm
&& ((cm0 && last_restart.elapsed().as_secs() > 60)
|| last_restart.elapsed().as_secs() > 3600)
{
// restart server if new connections all closed, or every one hour,
// as a workaround to resolve "SpotUdp" (dns resolve)
// and x server get displays failure issue
if let Some(ps) = server.as_mut() {
allow_err!(ps.kill());
std::thread::sleep(std::time::Duration::from_millis(30));
last_restart = std::time::Instant::now();
log::info!("restart server");
}
}
if let Some(ps) = server.as_mut() {
match ps.try_wait() {
Ok(Some(_)) => {
server = None;
start_new = true;
};
}
_ => {}
let mut d = get_env("DISPLAY", &uid);
if d.is_empty() {
d = get_display();
}
if d.is_empty() {
d = ":0".to_owned();
}
d = d.replace(&whoami::hostname(), "").replace("localhost", "");
log::info!("DISPLAY: {}", d);
log::info!("XAUTHORITY: {}", auth);
std::env::set_var("XAUTHORITY", auth);
std::env::set_var("DISPLAY", d);
if let Some(ps) = server.as_mut() {
allow_err!(ps.kill());
std::thread::sleep(std::time::Duration::from_millis(30));
last_restart = std::time::Instant::now();
}
} else if !cm
&& ((cm0 && last_restart.elapsed().as_secs() > 60)
|| last_restart.elapsed().as_secs() > 3600)
{
// restart server if new connections all closed, or every one hour,
// as a workaround to resolve "SpotUdp" (dns resolve)
// and x server get displays failure issue
if let Some(ps) = server.as_mut() {
allow_err!(ps.kill());
std::thread::sleep(std::time::Duration::from_millis(30));
last_restart = std::time::Instant::now();
log::info!("restart server");
}
}
if let Some(ps) = server.as_mut() {
match ps.try_wait() {
Ok(Some(_)) => {
server = None;
start_new = true;
}
_ => {}
}
} else {
start_new = true;
}
if start_new {
match crate::run_me(vec!["--server"]) {
Ok(ps) => server = Some(ps),
Err(err) => {
log::error!("Failed to start server: {}", err);
}
}
}
cm0 = cm;
} else if username != "" {
if username != "gdm" {
// try kill subprocess "--server"
stop_server(&mut server);
// try start user service
try_start_user_service(&username);
}
} else {
start_new = true;
try_stop_user_service();
stop_server(&mut server);
}
if start_new {
match crate::run_me(vec!["--server"]) {
Ok(ps) => server = Some(ps),
Err(err) => {
log::error!("Failed to start server: {}", err);
}
}
}
cm0 = cm;
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
}
try_stop_user_service();
if let Some(ps) = server.take().as_mut() {
allow_err!(ps.kill());
}
@@ -246,17 +337,6 @@ pub fn get_active_userid() -> String {
get_value_of_seat0(1)
}
fn is_active(sid: &str) -> bool {
if let Ok(output) = std::process::Command::new("loginctl")
.args(vec!["show-session", "-p", "State", sid])
.output()
{
String::from_utf8_lossy(&output.stdout).contains("active")
} else {
false
}
}
fn get_cm() -> bool {
if let Ok(output) = std::process::Command::new("ps").args(vec!["aux"]).output() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
@@ -312,89 +392,6 @@ fn get_display() -> String {
last
}
fn get_value_of_seat0(i: usize) -> String {
if let Ok(output) = std::process::Command::new("loginctl").output() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.contains("seat0") {
if let Some(sid) = line.split_whitespace().nth(0) {
if is_active(sid) {
if let Some(uid) = line.split_whitespace().nth(i) {
return uid.to_owned();
}
}
}
}
}
}
// some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73
if let Ok(output) = std::process::Command::new("loginctl").output() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
if let Some(sid) = line.split_whitespace().nth(0) {
let d = get_display_server_of_session(sid);
if is_active(sid) && d != "tty" {
if let Some(uid) = line.split_whitespace().nth(i) {
return uid.to_owned();
}
}
}
}
}
return "".to_owned();
}
pub fn get_display_server() -> String {
let session = get_value_of_seat0(0);
get_display_server_of_session(&session)
}
fn get_display_server_of_session(session: &str) -> String {
if let Ok(output) = std::process::Command::new("loginctl")
.args(vec!["show-session", "-p", "Type", session])
.output()
// Check session type of the session
{
let display_server = String::from_utf8_lossy(&output.stdout)
.replace("Type=", "")
.trim_end()
.into();
if display_server == "tty" {
// If the type is tty...
if let Ok(output) = std::process::Command::new("loginctl")
.args(vec!["show-session", "-p", "TTY", session])
.output()
// Get the tty number
{
let tty: String = String::from_utf8_lossy(&output.stdout)
.replace("TTY=", "")
.trim_end()
.into();
if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty))
// And check if Xorg is running on that tty
{
if xorg_results.trim_end().to_string() != "" {
// If it is, manually return "x11", otherwise return tty
"x11".to_owned()
} else {
display_server
}
} else {
// If any of these commands fail just fall back to the display server
display_server
}
} else {
display_server
}
} else {
// If the session is not a tty, then just return the type as usual
display_server
}
} else {
"".to_owned()
}
}
pub fn is_login_wayland() -> bool {
if let Ok(contents) = std::fs::read_to_string("/etc/gdm3/custom.conf") {
contents.contains("#WaylandEnable=false")
@@ -601,13 +598,6 @@ pub fn is_installed() -> bool {
true
}
pub fn run_cmds(cmds: String) -> ResultType<String> {
let output = std::process::Command::new("sh")
.args(vec!["-c", &cmds])
.output()?;
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
fn get_env_tries(name: &str, uid: &str, n: usize) -> String {
for _ in 0..n {
let x = get_env(name, uid);

View File

@@ -120,21 +120,21 @@ async fn connect_and_login(
Ok(Some(Ok(bytes))) => {
let msg_in = Message::parse_from_bytes(&bytes)?;
match msg_in.union {
Some(message::Union::hash(hash)) => {
Some(message::Union::Hash(hash)) => {
interface.handle_hash(hash, &mut stream).await;
}
Some(message::Union::login_response(lr)) => match lr.union {
Some(login_response::Union::error(err)) => {
Some(message::Union::LoginResponse(lr)) => match lr.union {
Some(login_response::Union::Error(err)) => {
interface.handle_login_error(&err);
return Ok(None);
}
Some(login_response::Union::peer_info(pi)) => {
Some(login_response::Union::PeerInfo(pi)) => {
interface.handle_peer_info(pi);
break;
}
_ => {}
}
Some(message::Union::test_delay(t)) => {
Some(message::Union::TestDelay(t)) => {
interface.handle_test_delay(t, &mut stream).await;
}
_ => {}
@@ -183,7 +183,7 @@ async fn run_forward(forward: Framed<TcpStream, BytesCodec>, stream: Stream) ->
},
res = stream.next() => {
if let Some(Ok(bytes)) = res {
allow_err!(forward.send(bytes.into()).await);
allow_err!(forward.send(bytes).await);
} else {
break;
}

View File

@@ -2,7 +2,7 @@ use crate::server::{check_zombie, new as new_server, ServerPtr};
use hbb_common::{
allow_err,
anyhow::bail,
config::{self, Config, REG_INTERVAL, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
config::{Config, REG_INTERVAL, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
futures::future::join_all,
log,
protobuf::Message as _,
@@ -51,9 +51,12 @@ impl RendezvousMediator {
check_zombie();
let server = new_server();
if Config::get_nat_type() == NatType::UNKNOWN_NAT as i32 {
crate::common::test_nat_type();
crate::test_nat_type();
nat_tested = true;
}
if !Config::get_option("stop-service").is_empty() {
crate::test_rendezvous_server();
}
let server_cloned = server.clone();
tokio::spawn(async move {
direct_server(server_cloned).await;
@@ -61,14 +64,14 @@ impl RendezvousMediator {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if crate::platform::is_installed() {
std::thread::spawn(move || {
allow_err!(lan_discovery());
allow_err!(super::lan::start_listening());
});
}
loop {
Config::reset_online();
if Config::get_option("stop-service").is_empty() {
if !nat_tested {
crate::common::test_nat_type();
crate::test_nat_type();
nat_tested = true;
}
let mut futs = Vec::new();
@@ -157,7 +160,7 @@ impl RendezvousMediator {
Some(Ok((bytes, _))) => {
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
match msg_in.union {
Some(rendezvous_message::Union::register_peer_response(rpr)) => {
Some(rendezvous_message::Union::RegisterPeerResponse(rpr)) => {
update_latency();
if rpr.request_pk {
log::info!("request_pk received from {}", host);
@@ -165,7 +168,7 @@ impl RendezvousMediator {
continue;
}
}
Some(rendezvous_message::Union::register_pk_response(rpr)) => {
Some(rendezvous_message::Union::RegisterPkResponse(rpr)) => {
update_latency();
match rpr.result.enum_value_or_default() {
register_pk_response::Result::OK => {
@@ -179,28 +182,28 @@ impl RendezvousMediator {
_ => {}
}
}
Some(rendezvous_message::Union::punch_hole(ph)) => {
Some(rendezvous_message::Union::PunchHole(ph)) => {
let rz = rz.clone();
let server = server.clone();
tokio::spawn(async move {
allow_err!(rz.handle_punch_hole(ph, server).await);
});
}
Some(rendezvous_message::Union::request_relay(rr)) => {
Some(rendezvous_message::Union::RequestRelay(rr)) => {
let rz = rz.clone();
let server = server.clone();
tokio::spawn(async move {
allow_err!(rz.handle_request_relay(rr, server).await);
});
}
Some(rendezvous_message::Union::fetch_local_addr(fla)) => {
Some(rendezvous_message::Union::FetchLocalAddr(fla)) => {
let rz = rz.clone();
let server = server.clone();
tokio::spawn(async move {
allow_err!(rz.handle_intranet(fla, server).await);
});
}
Some(rendezvous_message::Union::configure_update(cu)) => {
Some(rendezvous_message::Union::ConfigureUpdate(cu)) => {
let v0 = Config::get_rendezvous_servers();
Config::set_option("rendezvous-servers".to_owned(), cu.rendezvous_servers.join(","));
Config::set_serial(cu.serial);
@@ -367,7 +370,7 @@ impl RendezvousMediator {
socket
};
let mut msg_out = Message::new();
use hbb_common::protobuf::ProtobufEnum;
use hbb_common::protobuf::Enum;
let nat_type = NatType::from_i32(Config::get_nat_type()).unwrap_or(NatType::UNKNOWN_NAT);
msg_out.set_punch_hole_sent(PunchHoleSent {
socket_addr: ph.socket_addr,
@@ -386,7 +389,7 @@ impl RendezvousMediator {
async fn register_pk(&mut self, socket: &mut FramedSocket) -> ResultType<()> {
let mut msg_out = Message::new();
let pk = Config::get_key_pair().1;
let uuid = crate::get_uuid();
let uuid = hbb_common::get_uuid();
let id = Config::get_id();
self.last_id_pk_registry = id.clone();
msg_out.set_register_pk(RegisterPk {
@@ -537,103 +540,3 @@ 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::peer_discovery(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();
}
}
_ => {}
}
}
}
}
}
pub fn discover() -> ResultType<()> {
let addr = SocketAddr::from(([0, 0, 0, 0], 0));
let socket = std::net::UdpSocket::bind(addr)?;
socket.set_broadcast(true)?;
let mut msg_out = Message::new();
let peer = PeerDiscovery {
cmd: "ping".to_owned(),
..Default::default()
};
msg_out.set_peer_discovery(peer);
let maddr = SocketAddr::from(([255, 255, 255, 255], get_broadcast_port()));
socket.send_to(&msg_out.write_to_bytes()?, maddr)?;
log::info!("discover ping sent");
let mut last_recv_time = Instant::now();
let mut last_write_time = Instant::now();
let mut last_write_n = 0;
// to-do: load saved peers, and update incrementally (then we can see offline)
let mut peers = Vec::new();
let mac = get_mac();
socket.set_read_timeout(Some(std::time::Duration::from_millis(10)))?;
loop {
let mut buf = [0; 2048];
if let Ok((len, _)) = 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::peer_discovery(p)) => {
last_recv_time = Instant::now();
if p.cmd == "pong" {
if p.mac != mac {
peers.push((p.id, p.username, p.hostname, p.platform));
}
}
}
_ => {}
}
}
}
if last_write_time.elapsed().as_millis() > 300 && last_write_n != peers.len() {
config::LanPeers::store(serde_json::to_string(&peers)?);
last_write_time = Instant::now();
last_write_n = peers.len();
}
if last_recv_time.elapsed().as_millis() > 3_000 {
break;
}
}
log::info!("discover ping done");
config::LanPeers::store(serde_json::to_string(&peers)?);
Ok(())
}

View File

@@ -7,7 +7,7 @@ use hbb_common::{
config::{Config, Config2, CONNECT_TIMEOUT, RELAY_PORT},
log,
message_proto::*,
protobuf::{Message as _, ProtobufEnum},
protobuf::{Enum, Message as _},
rendezvous_proto::*,
socket_client,
sodiumoxide::crypto::{box_, secretbox, sign},
@@ -24,6 +24,10 @@ pub mod audio_service;
cfg_if::cfg_if! {
if #[cfg(not(any(target_os = "android", target_os = "ios")))] {
mod clipboard_service;
#[cfg(target_os = "linux")]
mod wayland;
#[cfg(target_os = "linux")]
pub mod uinput;
pub mod input_service;
} else {
mod clipboard_service {
@@ -140,7 +144,7 @@ pub async fn create_tcp_connection(
Some(res) => {
let bytes = res?;
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
if let Some(message::Union::public_key(pk)) = msg_in.union {
if let Some(message::Union::PublicKey(pk)) = msg_in.union {
if pk.asymmetric_value.len() == box_::PUBLICKEYBYTES {
let nonce = box_::Nonce([0u8; box_::NONCEBYTES]);
let mut pk_ = [0u8; box_::PUBLICKEYBYTES];
@@ -279,6 +283,8 @@ impl Drop for Server {
for s in self.services.values() {
s.join();
}
#[cfg(target_os = "linux")]
wayland::clear();
}
}

View File

@@ -8,6 +8,7 @@ use crate::video_service;
use crate::{common::MOBILE_INFO2, mobile::connection_manager::start_channel};
use crate::{ipc, VERSION};
use hbb_common::fs::can_enable_overwrite_detection;
use hbb_common::password_security::password;
use hbb_common::{
config::Config,
fs,
@@ -35,6 +36,7 @@ pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>;
lazy_static::lazy_static! {
static ref LOGIN_FAILURES: Arc::<Mutex<HashMap<String, (i32, i32, i32)>>> = Default::default();
static ref SESSIONS: Arc::<Mutex<HashMap<String, Session>>> = Default::default();
}
pub static CLICK_TIME: AtomicI64 = AtomicI64::new(0);
pub static MOUSE_MOVE_TIME: AtomicI64 = AtomicI64::new(0);
@@ -53,6 +55,14 @@ enum MessageInput {
BlockOff,
}
#[derive(Clone, Debug)]
struct Session {
name: String,
session_id: u64,
last_recv_time: Arc<Mutex<Instant>>,
random_password: String,
}
pub struct Connection {
inner: ConnInner,
stream: super::Stream,
@@ -80,6 +90,8 @@ pub struct Connection {
video_ack_required: bool,
peer_info: (String, String),
api_server: String,
lr: LoginRequest,
last_recv_time: Arc<Mutex<Instant>>,
}
impl Subscriber for ConnInner {
@@ -91,7 +103,7 @@ impl Subscriber for ConnInner {
#[inline]
fn send(&mut self, msg: Arc<Message>) {
match &msg.union {
Some(message::Union::video_frame(_)) => {
Some(message::Union::VideoFrame(_)) => {
self.tx_video.as_mut().map(|tx| {
allow_err!(tx.send((Instant::now(), msg)));
});
@@ -111,6 +123,7 @@ const H1: Duration = Duration::from_secs(3600);
const MILLI1: Duration = Duration::from_millis(1);
const SEND_TIMEOUT_VIDEO: u64 = 12_000;
const SEND_TIMEOUT_OTHER: u64 = SEND_TIMEOUT_VIDEO * 10;
const SESSION_TIMEOUT: Duration = Duration::from_secs(30);
impl Connection {
pub async fn start(
@@ -164,6 +177,8 @@ impl Connection {
video_ack_required: false,
peer_info: Default::default(),
api_server: "".to_owned(),
lr: Default::default(),
last_recv_time: Arc::new(Mutex::new(Instant::now())),
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
tokio::spawn(async move {
@@ -222,7 +237,8 @@ impl Connection {
let mut msg_out = Message::new();
msg_out.set_misc(misc);
conn.send(msg_out).await;
conn.on_close("Close requested from connection manager", false);
conn.on_close("Close requested from connection manager", false).await;
SESSIONS.lock().unwrap().remove(&conn.lr.my_id);
break;
}
ipc::Data::ChatMessage{text} => {
@@ -311,11 +327,12 @@ impl Connection {
if let Some(res) = res {
match res {
Err(err) => {
conn.on_close(&err.to_string(), true);
conn.on_close(&err.to_string(), true).await;
break;
},
Ok(bytes) => {
last_recv_time = Instant::now();
*conn.last_recv_time.lock().unwrap() = Instant::now();
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
if !conn.on_message(msg_in).await {
break;
@@ -324,14 +341,14 @@ impl Connection {
}
}
} else {
conn.on_close("Reset by the peer", true);
conn.on_close("Reset by the peer", true).await;
break;
}
},
_ = conn.timer.tick() => {
if !conn.read_jobs.is_empty() {
if let Err(err) = fs::handle_read_jobs(&mut conn.read_jobs, &mut conn.stream).await {
conn.on_close(&err.to_string(), false);
conn.on_close(&err.to_string(), false).await;
break;
}
} else {
@@ -344,7 +361,7 @@ impl Connection {
video_service::notify_video_frame_feched(id, Some(instant.into()));
}
if let Err(err) = conn.stream.send(&value as &Message).await {
conn.on_close(&err.to_string(), false);
conn.on_close(&err.to_string(), false).await;
break;
}
},
@@ -354,7 +371,7 @@ impl Connection {
if latency > 1000 {
match &msg.union {
Some(message::Union::audio_frame(_)) => {
Some(message::Union::AudioFrame(_)) => {
// log::info!("audio frame latency {}", instant.elapsed().as_secs_f32());
continue;
}
@@ -362,13 +379,13 @@ impl Connection {
}
}
if let Err(err) = conn.stream.send(msg).await {
conn.on_close(&err.to_string(), false);
conn.on_close(&err.to_string(), false).await;
break;
}
},
_ = test_delay_timer.tick() => {
if last_recv_time.elapsed() >= SEC30 {
conn.on_close("Timeout", true);
conn.on_close("Timeout", true).await;
break;
}
let time = crate::get_time();
@@ -398,8 +415,9 @@ impl Connection {
video_service::notify_video_frame_feched(id, None);
scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove);
video_service::VIDEO_QOS.lock().unwrap().reset();
password::after_session(conn.authorized);
if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
conn.on_close(&err.to_string(), false);
conn.on_close(&err.to_string(), false).await;
}
conn.post_audit(json!({
@@ -493,7 +511,7 @@ impl Connection {
res = self.stream.next() => {
if let Some(res) = res {
last_recv_time = Instant::now();
timeout(SEND_TIMEOUT_OTHER, forward.send(res?.into())).await??;
timeout(SEND_TIMEOUT_OTHER, forward.send(res?)).await??;
} else {
bail!("Stream reset by the peer");
}
@@ -572,7 +590,7 @@ impl Connection {
let url = self.api_server.clone();
let mut v = v;
v["id"] = json!(Config::get_id());
v["uuid"] = json!(base64::encode(crate::get_uuid()));
v["uuid"] = json!(base64::encode(hbb_common::get_uuid()));
v["Id"] = json!(self.inner.id);
tokio::spawn(async move {
allow_err!(Self::post_audit_async(url, v).await);
@@ -629,9 +647,9 @@ impl Connection {
#[cfg(target_os = "linux")]
if !self.file_transfer.is_some() && !self.port_forward_socket.is_some() {
let dtype = crate::platform::linux::get_display_server();
if dtype != "x11" {
if dtype != "x11" && dtype != "wayland" {
res.set_error(format!(
"Unsupported display server type {}, x11 expected",
"Unsupported display server type {}, x11 or wayland expected",
dtype
));
let mut msg_out = Message::new();
@@ -667,7 +685,7 @@ impl Connection {
res.set_peer_info(pi);
} else {
try_activate_screen();
match video_service::get_displays() {
match super::video_service::get_displays().await {
Err(err) => {
res.set_error(format!("X11 error: {}", err));
}
@@ -779,8 +797,77 @@ impl Connection {
self.tx_input.send(MessageInput::Key((msg, press))).ok();
}
fn validate_one_password(&self, password: String) -> bool {
if password.len() == 0 {
return false;
}
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(&self.hash.salt);
let mut hasher2 = Sha256::new();
hasher2.update(&hasher.finalize()[..]);
hasher2.update(&self.hash.challenge);
hasher2.finalize()[..] == self.lr.password[..]
}
fn validate_password(&mut self) -> bool {
if password::security_enabled() {
if self.validate_one_password(Config::get_security_password()) {
return true;
}
}
if password::random_password_valid() {
let password = password::random_password();
if self.validate_one_password(password.clone()) {
if password::onetime_password_activated() {
password::set_onetime_password_activated(false);
}
SESSIONS.lock().unwrap().insert(
self.lr.my_id.clone(),
Session {
name: self.lr.my_name.clone(),
session_id: self.lr.session_id,
last_recv_time: self.last_recv_time.clone(),
random_password: password,
},
);
return true;
}
}
false
}
fn is_of_recent_session(&mut self) -> bool {
let session = SESSIONS
.lock()
.unwrap()
.get(&self.lr.my_id)
.map(|s| s.to_owned());
if let Some(session) = session {
if session.name == self.lr.my_name
&& session.session_id == self.lr.session_id
&& !self.lr.password.is_empty()
&& self.validate_one_password(session.random_password.clone())
&& session.last_recv_time.lock().unwrap().elapsed() < SESSION_TIMEOUT
{
SESSIONS.lock().unwrap().insert(
self.lr.my_id.clone(),
Session {
name: self.lr.my_name.clone(),
session_id: self.lr.session_id,
last_recv_time: self.last_recv_time.clone(),
random_password: session.random_password,
},
);
return true;
}
}
false
}
async fn on_message(&mut self, msg: Message) -> bool {
if let Some(message::Union::login_request(lr)) = msg.union {
if let Some(message::Union::LoginRequest(lr)) = msg.union {
self.lr = lr.clone();
if let Some(o) = lr.option.as_ref() {
self.update_option(o).await;
if let Some(q) = o.video_codec_state.clone().take() {
@@ -805,7 +892,7 @@ impl Connection {
return true;
}
match lr.union {
Some(login_request::Union::file_transfer(ft)) => {
Some(login_request::Union::FileTransfer(ft)) => {
if !Config::get_option("enable-file-transfer").is_empty() {
self.send_login_error("No permission of file transfer")
.await;
@@ -814,7 +901,7 @@ impl Connection {
}
self.file_transfer = Some((ft.dir, ft.show_hidden));
}
Some(login_request::Union::port_forward(mut pf)) => {
Some(login_request::Union::PortForward(mut pf)) => {
if !Config::get_option("enable-tunnel").is_empty() {
self.send_login_error("No permission of IP tunneling").await;
sleep(1.).await;
@@ -851,15 +938,19 @@ impl Connection {
}
if !crate::is_ip(&lr.username) && lr.username != Config::get_id() {
self.send_login_error("Offline").await;
} else if self.is_of_recent_session() {
self.try_start_cm(lr.my_id, lr.my_name, true);
self.send_logon_response().await;
if self.port_forward_socket.is_some() {
return false;
}
} else if lr.password.is_empty() {
self.try_start_cm(lr.my_id, lr.my_name, false);
} else {
let mut hasher = Sha256::new();
hasher.update(&Config::get_password());
hasher.update(&self.hash.salt);
let mut hasher2 = Sha256::new();
hasher2.update(&hasher.finalize()[..]);
hasher2.update(&self.hash.challenge);
if password::passwords().len() == 0 {
self.send_login_error("Connection not allowed").await;
return false;
}
let mut failure = LOGIN_FAILURES
.lock()
.unwrap()
@@ -872,7 +963,7 @@ impl Connection {
.await;
} else if time == failure.0 && failure.1 > 6 {
self.send_login_error("Please try 1 minute later").await;
} else if hasher2.finalize()[..] != lr.password[..] {
} else if !self.validate_password() {
if failure.0 == time {
failure.1 += 1;
failure.2 += 1;
@@ -898,7 +989,7 @@ impl Connection {
}
}
}
} else if let Some(message::Union::test_delay(t)) = msg.union {
} else if let Some(message::Union::TestDelay(t)) = msg.union {
if t.from_client {
let mut msg_out = Message::new();
msg_out.set_test_delay(t);
@@ -913,7 +1004,7 @@ impl Connection {
}
} else if self.authorized {
match msg.union {
Some(message::Union::mouse_event(me)) => {
Some(message::Union::MouseEvent(me)) => {
#[cfg(any(target_os = "android", target_os = "ios"))]
if let Err(e) = call_main_service_mouse_input(me.mask, me.x, me.y) {
log::debug!("call_main_service_mouse_input fail:{}", e);
@@ -928,7 +1019,7 @@ impl Connection {
self.input_mouse(me, self.inner.id());
}
}
Some(message::Union::key_event(me)) => {
Some(message::Union::KeyEvent(me)) => {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.keyboard {
if is_enter(&me) {
@@ -944,8 +1035,8 @@ impl Connection {
};
if is_press {
match me.union {
Some(key_event::Union::unicode(_))
| Some(key_event::Union::seq(_)) => {
Some(key_event::Union::Unicode(_))
| Some(key_event::Union::Seq(_)) => {
self.input_key(me, false);
}
_ => {
@@ -957,14 +1048,14 @@ impl Connection {
}
}
}
Some(message::Union::clipboard(cb)) =>
Some(message::Union::Clipboard(cb)) =>
{
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.clipboard {
update_clipboard(cb, None);
}
}
Some(message::Union::cliprdr(_clip)) => {
Some(message::Union::Cliprdr(_clip)) => {
if self.file_transfer_enabled() {
#[cfg(windows)]
if let Some(clip) = msg_2_clip(_clip) {
@@ -972,13 +1063,13 @@ impl Connection {
}
}
}
Some(message::Union::file_action(fa)) => {
Some(message::Union::FileAction(fa)) => {
if self.file_transfer.is_some() {
match fa.union {
Some(file_action::Union::read_dir(rd)) => {
Some(file_action::Union::ReadDir(rd)) => {
self.read_dir(&rd.path, rd.include_hidden);
}
Some(file_action::Union::all_files(f)) => {
Some(file_action::Union::AllFiles(f)) => {
match fs::get_recursive_files(&f.path, f.include_hidden) {
Err(err) => {
self.send(fs::new_error(f.id, err, -1)).await;
@@ -988,7 +1079,7 @@ impl Connection {
}
}
}
Some(file_action::Union::send(s)) => {
Some(file_action::Union::Send(s)) => {
let id = s.id;
let od =
can_enable_overwrite_detection(get_version_number(VERSION));
@@ -1013,7 +1104,7 @@ impl Connection {
}
}
}
Some(file_action::Union::receive(r)) => {
Some(file_action::Union::Receive(r)) => {
self.send_fs(ipc::FS::NewWrite {
path: r.path,
id: r.id,
@@ -1026,31 +1117,31 @@ impl Connection {
.collect(),
});
}
Some(file_action::Union::remove_dir(d)) => {
Some(file_action::Union::RemoveDir(d)) => {
self.send_fs(ipc::FS::RemoveDir {
path: d.path,
id: d.id,
recursive: d.recursive,
});
}
Some(file_action::Union::remove_file(f)) => {
Some(file_action::Union::RemoveFile(f)) => {
self.send_fs(ipc::FS::RemoveFile {
path: f.path,
id: f.id,
file_num: f.file_num,
});
}
Some(file_action::Union::create(c)) => {
Some(file_action::Union::Create(c)) => {
self.send_fs(ipc::FS::CreateDir {
path: c.path,
id: c.id,
});
}
Some(file_action::Union::cancel(c)) => {
Some(file_action::Union::Cancel(c)) => {
self.send_fs(ipc::FS::CancelWrite { id: c.id });
fs::remove_job(c.id, &mut self.read_jobs);
}
Some(file_action::Union::send_confirm(r)) => {
Some(file_action::Union::SendConfirm(r)) => {
if let Some(job) = fs::get_job(r.id, &mut self.read_jobs) {
job.confirm(&r);
}
@@ -1059,8 +1150,8 @@ impl Connection {
}
}
}
Some(message::Union::file_response(fr)) => match fr.union {
Some(file_response::Union::block(block)) => {
Some(message::Union::FileResponse(fr)) => match fr.union {
Some(file_response::Union::Block(block)) => {
self.send_fs(ipc::FS::WriteBlock {
id: block.id,
file_num: block.file_num,
@@ -1068,13 +1159,13 @@ impl Connection {
compressed: block.compressed,
});
}
Some(file_response::Union::done(d)) => {
Some(file_response::Union::Done(d)) => {
self.send_fs(ipc::FS::WriteDone {
id: d.id,
file_num: d.file_num,
});
}
Some(file_response::Union::digest(d)) => self.send_fs(ipc::FS::CheckDigest {
Some(file_response::Union::Digest(d)) => self.send_fs(ipc::FS::CheckDigest {
id: d.id,
file_num: d.file_num,
file_size: d.file_size,
@@ -1083,27 +1174,32 @@ impl Connection {
}),
_ => {}
},
Some(message::Union::misc(misc)) => match misc.union {
Some(misc::Union::switch_display(s)) => {
video_service::switch_display(s.display);
Some(message::Union::Misc(misc)) => match misc.union {
Some(misc::Union::SwitchDisplay(s)) => {
video_service::switch_display(s.display).await;
}
Some(misc::Union::chat_message(c)) => {
Some(misc::Union::ChatMessage(c)) => {
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
}
Some(misc::Union::option(o)) => {
Some(misc::Union::Option(o)) => {
self.update_option(&o).await;
}
Some(misc::Union::refresh_video(r)) => {
Some(misc::Union::RefreshVideo(r)) => {
if r {
video_service::refresh();
super::video_service::refresh();
}
}
Some(misc::Union::video_received(_)) => {
Some(misc::Union::VideoReceived(_)) => {
video_service::notify_video_frame_feched(
self.inner.id,
Some(Instant::now().into()),
);
}
Some(misc::Union::CloseReason(_)) => {
self.on_close("Peer close", true).await;
SESSIONS.lock().unwrap().remove(&self.lr.my_id);
return false;
}
_ => {}
},
_ => {}
@@ -1258,14 +1354,14 @@ impl Connection {
}
}
fn on_close(&mut self, reason: &str, lock: bool) {
async fn on_close(&mut self, reason: &str, lock: bool) {
if let Some(s) = self.server.upgrade() {
s.write().unwrap().remove_connection(&self.inner);
}
log::info!("#{} Connection closed: {}", self.inner.id(), reason);
if lock && self.lock_after_session_end && self.keyboard {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
lock_screen();
lock_screen().await;
}
self.tx_to_cm.send(ipc::Data::Close).ok();
self.port_forward_socket.take();

View File

@@ -2,7 +2,7 @@ use super::*;
#[cfg(target_os = "macos")]
use dispatch::Queue;
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable};
use hbb_common::{config::COMPRESS_LEVEL, protobuf::ProtobufEnumOrUnknown};
use hbb_common::{config::COMPRESS_LEVEL, protobuf::ProtobufEnumOrUnknown, protobuf::EnumOrUnknown};
use rdev::{simulate, EventType, Key as RdevKey};
use std::{
convert::TryFrom,
@@ -69,7 +69,7 @@ impl Subscriber for MouseCursorSub {
#[inline]
fn send(&mut self, msg: Arc<Message>) {
if let Some(message::Union::cursor_data(cd)) = &msg.union {
if let Some(message::Union::CursorData(cd)) = &msg.union {
if let Some(msg) = self.cached.get(&cd.id) {
self.inner.send(msg.clone());
} else {
@@ -188,6 +188,26 @@ lazy_static::lazy_static! {
static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned());
}
#[cfg(target_os = "linux")]
pub async fn set_uinput() -> ResultType<()> {
// Keyboard and mouse both open /dev/uinput
// TODO: Make sure there's no race
let keyboard = super::uinput::client::UInputKeyboard::new().await?;
log::info!("UInput keyboard created");
let mouse = super::uinput::client::UInputMouse::new().await?;
log::info!("UInput mouse created");
let mut en = ENIGO.lock().unwrap();
en.set_uinput_keyboard(Some(Box::new(keyboard)));
en.set_uinput_mouse(Some(Box::new(mouse)));
Ok(())
}
#[cfg(target_os = "linux")]
pub async fn set_uinput_resolution(minx: i32, maxx: i32, miny: i32, maxy: i32) -> ResultType<()> {
super::uinput::client::set_resolution(minx, maxx, miny, maxy).await
}
pub fn is_left_up(evt: &MouseEvent) -> bool {
let buttons = evt.mask >> 3;
let evt_type = evt.mask & 0x7;
@@ -300,12 +320,12 @@ fn fix_key_down_timeout(force: bool) {
// e.g. current state of ctrl is down, but ctrl not in modifier, we should change ctrl to up, to make modifier state sync between remote and local
#[inline]
fn fix_modifier(
modifiers: &[ProtobufEnumOrUnknown<ControlKey>],
modifiers: &[EnumOrUnknown<ControlKey>],
key0: ControlKey,
key1: Key,
en: &mut Enigo,
) {
if get_modifier_state(key1, en) && !modifiers.contains(&ProtobufEnumOrUnknown::new(key0)) {
if get_modifier_state(key1, en) && !modifiers.contains(&EnumOrUnknown::new(key0)) {
#[cfg(windows)]
if key0 == ControlKey::Control && get_modifier_state(Key::Alt, en) {
// AltGr case
@@ -316,7 +336,7 @@ fn fix_modifier(
}
}
fn fix_modifiers(modifiers: &[ProtobufEnumOrUnknown<ControlKey>], en: &mut Enigo, ck: i32) {
fn fix_modifiers(modifiers: &[EnumOrUnknown<ControlKey>], en: &mut Enigo, ck: i32) {
if ck != ControlKey::Shift.value() {
fix_modifier(modifiers, ControlKey::Shift, Key::Shift, en);
}
@@ -431,7 +451,7 @@ fn handle_mouse_(evt: &MouseEvent, conn: i32) {
}
pub fn is_enter(evt: &KeyEvent) -> bool {
if let Some(key_event::Union::control_key(ck)) = evt.union {
if let Some(key_event::Union::ControlKey(ck)) = evt.union {
if ck.value() == ControlKey::Return.value() || ck.value() == ControlKey::NumpadEnter.value()
{
return true;
@@ -440,7 +460,7 @@ pub fn is_enter(evt: &KeyEvent) -> bool {
return false;
}
pub fn lock_screen() {
pub async fn lock_screen() {
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
// xdg_screensaver lock not work on Linux from our service somehow
@@ -477,7 +497,7 @@ pub fn lock_screen() {
crate::platform::lock_screen();
}
}
super::video_service::switch_to_primary();
super::video_service::switch_to_primary().await;
}
lazy_static::lazy_static! {
@@ -556,7 +576,6 @@ lazy_static::lazy_static! {
(ControlKey::Equals, Key::Equals),
(ControlKey::NumpadEnter, Key::NumpadEnter),
(ControlKey::RAlt, Key::RightAlt),
(ControlKey::RWin, Key::RWin),
(ControlKey::RControl, Key::RightControl),
(ControlKey::RShift, Key::RightShift),
].iter().map(|(a, b)| (a.value(), b.clone())).collect();
@@ -656,7 +675,7 @@ fn legacy_keyboard_map(evt: &KeyEvent) {
#[cfg(windows)]
let mut has_numlock = false;
if evt.down {
let ck = if let Some(key_event::Union::control_key(ck)) = evt.union {
let ck = if let Some(key_event::Union::ControlKey(ck)) = evt.union {
ck.value()
} else {
-1
@@ -711,7 +730,7 @@ fn legacy_keyboard_map(evt: &KeyEvent) {
}
}
match evt.union {
Some(key_event::Union::control_key(ck)) => {
Some(key_event::Union::ControlKey(ck)) => {
if let Some(key) = KEY_MAP.get(&ck.value()) {
#[cfg(windows)]
if let Some(_) = NUMPAD_KEY_MAP.get(&ck.value()) {
@@ -737,10 +756,10 @@ fn legacy_keyboard_map(evt: &KeyEvent) {
allow_err!(send_sas());
});
} else if ck.value() == ControlKey::LockScreen.value() {
lock_screen();
lock_screen_2();
}
}
Some(key_event::Union::chr(chr)) => {
Some(key_event::Union::Chr(chr)) => {
if evt.down {
if en.key_down(get_layout(chr)).is_ok() {
KEYS_DOWN
@@ -766,12 +785,12 @@ fn legacy_keyboard_map(evt: &KeyEvent) {
.remove(&(chr as u64 + KEY_CHAR_START));
}
}
Some(key_event::Union::unicode(chr)) => {
Some(key_event::Union::Unicode(chr)) => {
if let Ok(chr) = char::try_from(chr) {
en.key_sequence(&chr.to_string());
}
}
Some(key_event::Union::seq(ref seq)) => {
Some(key_event::Union::Seq(ref seq)) => {
en.key_sequence(&seq);
}
_ => {}
@@ -805,6 +824,11 @@ fn handle_key_(evt: &KeyEvent) {
}
}
#[tokio::main(flavor = "current_thread")]
async fn lock_screen_2() {
lock_screen().await;
}
#[tokio::main(flavor = "current_thread")]
async fn send_sas() -> ResultType<()> {
let mut stream = crate::ipc::connect(1000, crate::POSTFIX_SERVICE).await?;

651
src/server/uinput.rs Normal file
View File

@@ -0,0 +1,651 @@
use crate::ipc::{self, new_listener, Connection, Data, DataKeyboard, DataMouse};
use enigo::{Key, KeyboardControllable, MouseButton, MouseControllable};
use evdev::{
uinput::{VirtualDevice, VirtualDeviceBuilder},
AttributeSet, EventType, InputEvent,
};
use hbb_common::{allow_err, bail, log, tokio, ResultType};
static IPC_CONN_TIMEOUT: u64 = 1000;
static IPC_REQUEST_TIMEOUT: u64 = 1000;
static IPC_POSTFIX_KEYBOARD: &str = "_uinput_keyboard";
static IPC_POSTFIX_MOUSE: &str = "_uinput_mouse";
static IPC_POSTFIX_CONTROL: &str = "_uinput_control";
pub mod client {
use super::*;
pub struct UInputKeyboard {
conn: Connection,
}
impl UInputKeyboard {
pub async fn new() -> ResultType<Self> {
let conn = ipc::connect(IPC_CONN_TIMEOUT, IPC_POSTFIX_KEYBOARD).await?;
Ok(Self { conn })
}
#[tokio::main(flavor = "current_thread")]
async fn send(&mut self, data: Data) -> ResultType<()> {
self.conn.send(&data).await
}
#[tokio::main(flavor = "current_thread")]
async fn send_get_key_state(&mut self, data: Data) -> ResultType<bool> {
self.conn.send(&data).await?;
match self.conn.next_timeout(IPC_REQUEST_TIMEOUT).await {
Ok(Some(Data::KeyboardResponse(ipc::DataKeyboardResponse::GetKeyState(state)))) => {
Ok(state)
}
Ok(Some(resp)) => {
// FATAL error!!!
bail!(
"FATAL error, wait keyboard result other response: {:?}",
&resp
);
}
Ok(None) => {
// FATAL error!!!
// Maybe wait later
bail!("FATAL error, wait keyboard result, receive None",);
}
Err(e) => {
// FATAL error!!!
bail!(
"FATAL error, wait keyboard result timeout {}, {}",
&e,
IPC_REQUEST_TIMEOUT
);
}
}
}
}
impl KeyboardControllable for UInputKeyboard {
fn get_key_state(&mut self, key: Key) -> bool {
match self.send_get_key_state(Data::Keyboard(DataKeyboard::GetKeyState(key))) {
Ok(state) => state,
Err(e) => {
// unreachable!()
log::error!("Failed to get key state {}", &e);
false
}
}
}
fn key_sequence(&mut self, sequence: &str) {
allow_err!(self.send(Data::Keyboard(DataKeyboard::Sequence(sequence.to_string()))));
}
// TODO: handle error???
fn key_down(&mut self, key: Key) -> enigo::ResultType {
allow_err!(self.send(Data::Keyboard(DataKeyboard::KeyDown(key))));
Ok(())
}
fn key_up(&mut self, key: Key) {
allow_err!(self.send(Data::Keyboard(DataKeyboard::KeyUp(key))));
}
fn key_click(&mut self, key: Key) {
allow_err!(self.send(Data::Keyboard(DataKeyboard::KeyClick(key))));
}
}
pub struct UInputMouse {
conn: Connection,
}
impl UInputMouse {
pub async fn new() -> ResultType<Self> {
let conn = ipc::connect(IPC_CONN_TIMEOUT, IPC_POSTFIX_MOUSE).await?;
Ok(Self { conn })
}
#[tokio::main(flavor = "current_thread")]
async fn send(&mut self, data: Data) -> ResultType<()> {
self.conn.send(&data).await
}
}
impl MouseControllable for UInputMouse {
fn mouse_move_to(&mut self, x: i32, y: i32) {
allow_err!(self.send(Data::Mouse(DataMouse::MoveTo(x, y))));
}
fn mouse_move_relative(&mut self, x: i32, y: i32) {
allow_err!(self.send(Data::Mouse(DataMouse::MoveRelative(x, y))));
}
// TODO: handle error???
fn mouse_down(&mut self, button: MouseButton) -> enigo::ResultType {
allow_err!(self.send(Data::Mouse(DataMouse::Down(button))));
Ok(())
}
fn mouse_up(&mut self, button: MouseButton) {
allow_err!(self.send(Data::Mouse(DataMouse::Up(button))));
}
fn mouse_click(&mut self, button: MouseButton) {
allow_err!(self.send(Data::Mouse(DataMouse::Click(button))));
}
fn mouse_scroll_x(&mut self, length: i32) {
allow_err!(self.send(Data::Mouse(DataMouse::ScrollX(length))));
}
fn mouse_scroll_y(&mut self, length: i32) {
allow_err!(self.send(Data::Mouse(DataMouse::ScrollY(length))));
}
}
pub async fn set_resolution(minx: i32, maxx: i32, miny: i32, maxy: i32) -> ResultType<()> {
let mut conn = ipc::connect(IPC_CONN_TIMEOUT, IPC_POSTFIX_CONTROL).await?;
conn.send(&Data::Control(ipc::DataControl::Resolution {
minx,
maxx,
miny,
maxy,
}))
.await?;
let _ = conn.next().await?;
Ok(())
}
}
pub mod service {
use super::*;
use hbb_common::lazy_static;
use mouce::MouseActions;
use std::{collections::HashMap, sync::Mutex};
lazy_static::lazy_static! {
static ref KEY_MAP: HashMap<enigo::Key, evdev::Key> = HashMap::from(
[
(enigo::Key::Alt, evdev::Key::KEY_LEFTALT),
(enigo::Key::Backspace, evdev::Key::KEY_BACKSPACE),
(enigo::Key::CapsLock, evdev::Key::KEY_CAPSLOCK),
(enigo::Key::Control, evdev::Key::KEY_LEFTCTRL),
(enigo::Key::Delete, evdev::Key::KEY_DELETE),
(enigo::Key::DownArrow, evdev::Key::KEY_DOWN),
(enigo::Key::End, evdev::Key::KEY_END),
(enigo::Key::Escape, evdev::Key::KEY_ESC),
(enigo::Key::F1, evdev::Key::KEY_F1),
(enigo::Key::F10, evdev::Key::KEY_F10),
(enigo::Key::F11, evdev::Key::KEY_F11),
(enigo::Key::F12, evdev::Key::KEY_F12),
(enigo::Key::F2, evdev::Key::KEY_F2),
(enigo::Key::F3, evdev::Key::KEY_F3),
(enigo::Key::F4, evdev::Key::KEY_F4),
(enigo::Key::F5, evdev::Key::KEY_F5),
(enigo::Key::F6, evdev::Key::KEY_F6),
(enigo::Key::F7, evdev::Key::KEY_F7),
(enigo::Key::F8, evdev::Key::KEY_F8),
(enigo::Key::F9, evdev::Key::KEY_F9),
(enigo::Key::Home, evdev::Key::KEY_HOME),
(enigo::Key::LeftArrow, evdev::Key::KEY_LEFT),
(enigo::Key::Meta, evdev::Key::KEY_LEFTMETA),
(enigo::Key::Option, evdev::Key::KEY_OPTION),
(enigo::Key::PageDown, evdev::Key::KEY_PAGEDOWN),
(enigo::Key::PageUp, evdev::Key::KEY_PAGEUP),
(enigo::Key::Return, evdev::Key::KEY_ENTER),
(enigo::Key::RightArrow, evdev::Key::KEY_RIGHT),
(enigo::Key::Shift, evdev::Key::KEY_LEFTSHIFT),
(enigo::Key::Space, evdev::Key::KEY_SPACE),
(enigo::Key::Tab, evdev::Key::KEY_TAB),
(enigo::Key::UpArrow, evdev::Key::KEY_UP),
(enigo::Key::Numpad0, evdev::Key::KEY_KP0), // check if correct?
(enigo::Key::Numpad1, evdev::Key::KEY_KP1),
(enigo::Key::Numpad2, evdev::Key::KEY_KP2),
(enigo::Key::Numpad3, evdev::Key::KEY_KP3),
(enigo::Key::Numpad4, evdev::Key::KEY_KP4),
(enigo::Key::Numpad5, evdev::Key::KEY_KP5),
(enigo::Key::Numpad6, evdev::Key::KEY_KP6),
(enigo::Key::Numpad7, evdev::Key::KEY_KP7),
(enigo::Key::Numpad8, evdev::Key::KEY_KP8),
(enigo::Key::Numpad9, evdev::Key::KEY_KP9),
(enigo::Key::Cancel, evdev::Key::KEY_CANCEL),
(enigo::Key::Clear, evdev::Key::KEY_CLEAR),
(enigo::Key::Alt, evdev::Key::KEY_LEFTALT),
(enigo::Key::Pause, evdev::Key::KEY_PAUSE),
(enigo::Key::Kana, evdev::Key::KEY_KATAKANA), // check if correct?
(enigo::Key::Hangul, evdev::Key::KEY_HANGEUL), // check if correct?
// (enigo::Key::Junja, evdev::Key::KEY_JUNJA), // map?
// (enigo::Key::Final, evdev::Key::KEY_FINAL), // map?
(enigo::Key::Hanja, evdev::Key::KEY_HANJA),
// (enigo::Key::Kanji, evdev::Key::KEY_KANJI), // map?
// (enigo::Key::Convert, evdev::Key::KEY_CONVERT),
(enigo::Key::Select, evdev::Key::KEY_SELECT),
(enigo::Key::Print, evdev::Key::KEY_PRINT),
// (enigo::Key::Execute, evdev::Key::KEY_EXECUTE),
// (enigo::Key::Snapshot, evdev::Key::KEY_SNAPSHOT),
(enigo::Key::Insert, evdev::Key::KEY_INSERT),
(enigo::Key::Help, evdev::Key::KEY_HELP),
(enigo::Key::Sleep, evdev::Key::KEY_SLEEP),
// (enigo::Key::Separator, evdev::Key::KEY_SEPARATOR),
(enigo::Key::Scroll, evdev::Key::KEY_SCROLLLOCK),
(enigo::Key::NumLock, evdev::Key::KEY_NUMLOCK),
(enigo::Key::RWin, evdev::Key::KEY_RIGHTMETA),
(enigo::Key::Apps, evdev::Key::KEY_CONTEXT_MENU),
(enigo::Key::Multiply, evdev::Key::KEY_KPASTERISK),
(enigo::Key::Add, evdev::Key::KEY_KPPLUS),
(enigo::Key::Subtract, evdev::Key::KEY_KPMINUS),
(enigo::Key::Decimal, evdev::Key::KEY_KPCOMMA), // KEY_KPDOT and KEY_KPCOMMA are exchanged?
(enigo::Key::Divide, evdev::Key::KEY_KPSLASH),
(enigo::Key::Equals, evdev::Key::KEY_KPEQUAL),
(enigo::Key::NumpadEnter, evdev::Key::KEY_KPENTER),
(enigo::Key::RightAlt, evdev::Key::KEY_RIGHTALT),
(enigo::Key::RightControl, evdev::Key::KEY_RIGHTCTRL),
(enigo::Key::RightShift, evdev::Key::KEY_RIGHTSHIFT),
]);
static ref KEY_MAP_LAYOUT: HashMap<char, evdev::Key> = HashMap::from(
[
('a', evdev::Key::KEY_A),
('b', evdev::Key::KEY_B),
('c', evdev::Key::KEY_C),
('d', evdev::Key::KEY_D),
('e', evdev::Key::KEY_E),
('f', evdev::Key::KEY_F),
('g', evdev::Key::KEY_G),
('h', evdev::Key::KEY_H),
('i', evdev::Key::KEY_I),
('j', evdev::Key::KEY_J),
('k', evdev::Key::KEY_K),
('l', evdev::Key::KEY_L),
('m', evdev::Key::KEY_M),
('n', evdev::Key::KEY_N),
('o', evdev::Key::KEY_O),
('p', evdev::Key::KEY_P),
('q', evdev::Key::KEY_Q),
('r', evdev::Key::KEY_R),
('s', evdev::Key::KEY_S),
('t', evdev::Key::KEY_T),
('u', evdev::Key::KEY_U),
('v', evdev::Key::KEY_V),
('w', evdev::Key::KEY_W),
('x', evdev::Key::KEY_X),
('y', evdev::Key::KEY_Y),
('z', evdev::Key::KEY_Z),
('0', evdev::Key::KEY_0),
('1', evdev::Key::KEY_1),
('2', evdev::Key::KEY_2),
('3', evdev::Key::KEY_3),
('4', evdev::Key::KEY_4),
('5', evdev::Key::KEY_5),
('6', evdev::Key::KEY_6),
('7', evdev::Key::KEY_7),
('8', evdev::Key::KEY_8),
('9', evdev::Key::KEY_9),
('`', evdev::Key::KEY_GRAVE),
('-', evdev::Key::KEY_MINUS),
('=', evdev::Key::KEY_EQUAL),
('[', evdev::Key::KEY_LEFTBRACE),
(']', evdev::Key::KEY_RIGHTBRACE),
('\\', evdev::Key::KEY_BACKSLASH),
(',', evdev::Key::KEY_COMMA),
('.', evdev::Key::KEY_DOT),
('/', evdev::Key::KEY_SLASH),
(';', evdev::Key::KEY_SEMICOLON),
('\'', evdev::Key::KEY_APOSTROPHE),
]);
// ((minx, maxx), (miny, maxy))
static ref RESOLUTION: Mutex<((i32, i32), (i32, i32))> = Mutex::new(((0, 0), (0, 0)));
}
fn create_uinput_keyboard() -> ResultType<VirtualDevice> {
// TODO: ensure keys here
let mut keys = AttributeSet::<evdev::Key>::new();
for i in evdev::Key::KEY_ESC.code()..(evdev::Key::BTN_TRIGGER_HAPPY40.code() + 1) {
let key = evdev::Key::new(i);
if !format!("{:?}", &key).contains("unknown key") {
keys.insert(key);
}
}
let mut leds = AttributeSet::<evdev::LedType>::new();
leds.insert(evdev::LedType::LED_NUML);
leds.insert(evdev::LedType::LED_CAPSL);
leds.insert(evdev::LedType::LED_SCROLLL);
let mut miscs = AttributeSet::<evdev::MiscType>::new();
miscs.insert(evdev::MiscType::MSC_SCAN);
let keyboard = VirtualDeviceBuilder::new()?
.name("RustDesk UInput Keyboard")
.with_keys(&keys)?
.with_leds(&leds)?
.with_miscs(&miscs)?
.build()?;
Ok(keyboard)
}
fn map_key(key: &enigo::Key) -> ResultType<evdev::Key> {
if let Some(k) = KEY_MAP.get(&key) {
log::trace!("mapkey {:?}, get {:?}", &key, &k);
return Ok(k.clone());
} else {
match key {
enigo::Key::Layout(c) => {
if let Some(k) = KEY_MAP_LAYOUT.get(&c) {
log::trace!("mapkey {:?}, get {:?}", &key, k);
return Ok(k.clone());
}
}
// enigo::Key::Raw(c) => {
// let k = evdev::Key::new(c);
// if !format!("{:?}", &k).contains("unknown key") {
// return Ok(k.clone());
// }
// }
_ => {}
}
}
bail!("Failed to map key {:?}", &key);
}
async fn ipc_send_data(stream: &mut Connection, data: &Data) {
allow_err!(stream.send(data).await);
}
async fn handle_keyboard(
stream: &mut Connection,
keyboard: &mut VirtualDevice,
data: &DataKeyboard,
) {
log::trace!("handle_keyboard {:?}", &data);
match data {
DataKeyboard::Sequence(_seq) => {
// ignore
}
DataKeyboard::KeyDown(key) => {
if let Ok(k) = map_key(key) {
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
allow_err!(keyboard.emit(&[down_event]));
}
}
DataKeyboard::KeyUp(key) => {
if let Ok(k) = map_key(key) {
let up_event = InputEvent::new(EventType::KEY, k.code(), 0);
allow_err!(keyboard.emit(&[up_event]));
}
}
DataKeyboard::KeyClick(key) => {
if let Ok(k) = map_key(key) {
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
let up_event = InputEvent::new(EventType::KEY, k.code(), 0);
allow_err!(keyboard.emit(&[down_event, up_event]));
}
}
DataKeyboard::GetKeyState(key) => {
let key_state = if enigo::Key::CapsLock == *key {
match keyboard.get_led_state() {
Ok(leds) => leds.contains(evdev::LedType::LED_CAPSL),
Err(_e) => {
// log::debug!("Failed to get led state {}", &_e);
false
}
}
} else {
match keyboard.get_key_state() {
Ok(keys) => match key {
enigo::Key::Shift => {
keys.contains(evdev::Key::KEY_LEFTSHIFT)
|| keys.contains(evdev::Key::KEY_RIGHTSHIFT)
}
enigo::Key::Control => {
keys.contains(evdev::Key::KEY_LEFTCTRL)
|| keys.contains(evdev::Key::KEY_RIGHTCTRL)
}
enigo::Key::Alt => {
keys.contains(evdev::Key::KEY_LEFTALT)
|| keys.contains(evdev::Key::KEY_RIGHTALT)
}
enigo::Key::NumLock => keys.contains(evdev::Key::KEY_NUMLOCK),
enigo::Key::Meta => {
keys.contains(evdev::Key::KEY_LEFTMETA)
|| keys.contains(evdev::Key::KEY_RIGHTMETA)
}
_ => false,
},
Err(_e) => {
// log::debug!("Failed to get key state: {}", &_e);
false
}
}
};
ipc_send_data(
stream,
&Data::KeyboardResponse(ipc::DataKeyboardResponse::GetKeyState(key_state)),
)
.await;
}
}
}
fn handle_mouse(mouse: &mut mouce::nix::UInputMouseManager, data: &DataMouse) {
log::trace!("handle_mouse {:?}", &data);
match data {
DataMouse::MoveTo(x, y) => {
allow_err!(mouse.move_to(*x as _, *y as _))
}
DataMouse::MoveRelative(x, y) => {
allow_err!(mouse.move_relative(*x, *y))
}
DataMouse::Down(button) => {
let btn = match button {
enigo::MouseButton::Left => mouce::common::MouseButton::Left,
enigo::MouseButton::Middle => mouce::common::MouseButton::Middle,
enigo::MouseButton::Right => mouce::common::MouseButton::Right,
_ => {
return;
}
};
allow_err!(mouse.press_button(&btn))
}
DataMouse::Up(button) => {
let btn = match button {
enigo::MouseButton::Left => mouce::common::MouseButton::Left,
enigo::MouseButton::Middle => mouce::common::MouseButton::Middle,
enigo::MouseButton::Right => mouce::common::MouseButton::Right,
_ => {
return;
}
};
allow_err!(mouse.release_button(&btn))
}
DataMouse::Click(button) => {
let btn = match button {
enigo::MouseButton::Left => mouce::common::MouseButton::Left,
enigo::MouseButton::Middle => mouce::common::MouseButton::Middle,
enigo::MouseButton::Right => mouce::common::MouseButton::Right,
_ => {
return;
}
};
allow_err!(mouse.click_button(&btn))
}
DataMouse::ScrollX(_length) => {
// TODO: not supported for now
}
DataMouse::ScrollY(length) => {
let mut length = *length;
let scroll = if length < 0 {
mouce::common::ScrollDirection::Up
} else {
mouce::common::ScrollDirection::Down
};
if length < 0 {
length = -length;
}
for _ in 0..length {
allow_err!(mouse.scroll_wheel(&scroll))
}
}
}
}
fn spawn_keyboard_handler(mut stream: Connection) {
tokio::spawn(async move {
let mut keyboard = match create_uinput_keyboard() {
Ok(keyboard) => keyboard,
Err(e) => {
log::error!("Failed to create keyboard {}", e);
return;
}
};
loop {
tokio::select! {
res = stream.next() => {
match res {
Err(err) => {
log::info!("UInput keyboard ipc connection closed: {}", err);
break;
}
Ok(Some(data)) => {
match data {
Data::Keyboard(data) => {
handle_keyboard(&mut stream, &mut keyboard, &data).await;
}
_ => {
}
}
}
_ => {}
}
}
}
}
});
}
fn spawn_mouse_handler(mut stream: ipc::Connection) {
let resolution = RESOLUTION.lock().unwrap();
if resolution.0 .0 == resolution.0 .1 || resolution.1 .0 == resolution.1 .1 {
return;
}
let rng_x = resolution.0.clone();
let rng_y = resolution.1.clone();
tokio::spawn(async move {
log::info!(
"Create uinput mouce with rng_x: ({}, {}), rng_y: ({}, {})",
rng_x.0,
rng_x.1,
rng_y.0,
rng_y.1
);
let mut mouse = match mouce::Mouse::new_uinput(rng_x, rng_y) {
Ok(mouse) => mouse,
Err(e) => {
log::error!("Failed to create mouse, {}", e);
return;
}
};
loop {
tokio::select! {
res = stream.next() => {
match res {
Err(err) => {
log::info!("UInput mouse ipc connection closed: {}", err);
break;
}
Ok(Some(data)) => {
match data {
Data::Mouse(data) => {
handle_mouse(&mut mouse, &data);
}
_ => {
}
}
}
_ => {}
}
}
}
}
});
}
fn spawn_controller_handler(mut stream: ipc::Connection) {
tokio::spawn(async move {
loop {
tokio::select! {
res = stream.next() => {
match res {
Err(_err) => {
// log::info!("UInput controller ipc connection closed: {}", err);
break;
}
Ok(Some(data)) => {
match data {
Data::Control(data) => match data {
ipc::DataControl::Resolution{
minx,
maxx,
miny,
maxy,
} => {
*RESOLUTION.lock().unwrap() = ((minx, maxx), (miny, maxy));
allow_err!(stream.send(&Data::Empty).await);
}
}
_ => {
}
}
}
_ => {}
}
}
}
}
});
}
/// Start uinput service.
async fn start_service<F: FnOnce(ipc::Connection) + Copy>(postfix: &str, handler: F) {
match new_listener(postfix).await {
Ok(mut incoming) => {
while let Some(result) = incoming.next().await {
match result {
Ok(stream) => {
log::debug!("Got new connection of uinput ipc {}", postfix);
handler(Connection::new(stream));
}
Err(err) => {
log::error!("Couldn't get uinput mouse client: {:?}", err);
}
}
}
}
Err(err) => {
log::error!("Failed to start uinput mouse ipc service: {}", err);
}
}
}
/// Start uinput keyboard service.
#[tokio::main(flavor = "current_thread")]
pub async fn start_service_keyboard() {
log::info!("start uinput keyboard service");
start_service(IPC_POSTFIX_KEYBOARD, spawn_keyboard_handler).await;
}
/// Start uinput mouse service.
#[tokio::main(flavor = "current_thread")]
pub async fn start_service_mouse() {
log::info!("start uinput mouse service");
start_service(IPC_POSTFIX_MOUSE, spawn_mouse_handler).await;
}
/// Start uinput mouse service.
#[tokio::main(flavor = "current_thread")]
pub async fn start_service_control() {
log::info!("start uinput control service");
start_service(IPC_POSTFIX_CONTROL, spawn_controller_handler).await;
}
pub fn stop_service_keyboard() {
log::info!("stop uinput keyboard service");
}
pub fn stop_service_mouse() {
log::info!("stop uinput mouse service");
}
pub fn stop_service_control() {
log::info!("stop uinput control service");
}
}

View File

@@ -147,7 +147,6 @@ impl VideoQoS {
// handle image_quality change from peer
pub fn update_image_quality(&mut self, image_quality: i32) {
let image_quality = Self::convert_quality(image_quality) as _;
log::debug!("VideoQoS update_image_quality: {}", image_quality);
if self.current_image_quality != image_quality {
self.current_image_quality = image_quality;
let _ = self.generate_bitrate().ok();
@@ -171,7 +170,7 @@ impl VideoQoS {
#[cfg(target_os = "android")]
{
// fix when andorid screen shrinks
let fix = Display::fix_quality() as u32;
let fix = scrap::Display::fix_quality() as u32;
log::debug!("Android screen, fix quality:{}", fix);
let base_bitrate = base_bitrate * fix;
self.target_bitrate = base_bitrate * self.current_image_quality / 100;

View File

@@ -31,6 +31,7 @@ use scrap::{
use std::{
collections::HashSet,
io::{ErrorKind::WouldBlock, Result},
ops::{Deref, DerefMut},
time::{self, Duration, Instant},
};
#[cfg(windows)]
@@ -127,9 +128,11 @@ impl VideoFrameController {
}
}
trait TraitCapturer {
pub(super) trait TraitCapturer {
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>>;
fn set_use_yuv(&mut self, use_yuv: bool);
#[cfg(windows)]
fn is_gdi(&self) -> bool;
#[cfg(windows)]
@@ -141,6 +144,10 @@ impl TraitCapturer for Capturer {
self.frame(timeout)
}
fn set_use_yuv(&mut self, use_yuv: bool) {
self.set_use_yuv(use_yuv);
}
#[cfg(windows)]
fn is_gdi(&self) -> bool {
self.is_gdi()
@@ -158,6 +165,10 @@ impl TraitCapturer for scrap::CapturerMag {
self.frame(_timeout_ms)
}
fn set_use_yuv(&mut self, use_yuv: bool) {
self.set_use_yuv(use_yuv);
}
fn is_gdi(&self) -> bool {
false
}
@@ -179,6 +190,14 @@ fn check_display_changed(
last_width: usize,
last_hegiht: usize,
) -> bool {
#[cfg(target_os = "linux")]
{
// wayland do not support changing display for now
if !scrap::is_x11() {
return false;
}
}
let displays = match try_get_displays() {
Ok(d) => d,
_ => return false,
@@ -293,6 +312,7 @@ fn ensure_close_virtual_device() -> ResultType<()> {
Ok(())
}
// This function works on privacy mode. Windows only for now.
pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
let test_begin = Instant::now();
while test_begin.elapsed().as_millis() < timeout_millis as _ {
@@ -321,9 +341,38 @@ fn check_uac_switch(privacy_mode_id: i32, captuerer_privacy_mode_id: i32) -> Res
Ok(())
}
fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)]
ensure_close_virtual_device()?;
pub(super) struct CapturerInfo {
pub origin: (i32, i32),
pub width: usize,
pub height: usize,
pub ndisplay: usize,
pub current: usize,
pub privacy_mode_id: i32,
pub _captuerer_privacy_mode_id: i32,
pub capturer: Box<dyn TraitCapturer>,
}
impl Deref for CapturerInfo {
type Target = Box<dyn TraitCapturer>;
fn deref(&self) -> &Self::Target {
&self.capturer
}
}
impl DerefMut for CapturerInfo {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.capturer
}
}
fn get_capturer(use_yuv: bool) -> ResultType<CapturerInfo> {
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
return super::wayland::get_capturer();
}
}
let (ndisplay, current, display) = get_current_display()?;
let (origin, width, height) = (display.origin(), display.width(), display.height());
@@ -338,38 +387,6 @@ fn run(sp: GenericService) -> ResultType<()> {
num_cpus::get(),
);
let mut video_qos = VIDEO_QOS.lock().unwrap();
video_qos.set_size(width as _, height as _);
let mut spf = video_qos.spf();
let bitrate = video_qos.generate_bitrate()?;
let abr = video_qos.check_abr_config();
drop(video_qos);
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
let encoder_cfg = match Encoder::current_hw_encoder_name() {
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
codec_name,
width,
height,
bitrate: bitrate as _,
}),
None => EncoderCfg::VPX(VpxEncoderConfig {
width: width as _,
height: height as _,
timebase: [1, 1000], // Output timestamp precision
bitrate,
codec: VpxVideoCodecId::VP9,
num_threads: (num_cpus::get() / 2) as _,
}),
};
let mut encoder;
match Encoder::new(encoder_cfg) {
Ok(x) => encoder = x,
Err(err) => bail!("Failed to create encoder: {}", err),
}
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
#[cfg(not(windows))]
let captuerer_privacy_mode_id = privacy_mode_id;
@@ -389,17 +406,67 @@ fn run(sp: GenericService) -> ResultType<()> {
} else {
log::info!("In privacy mode, the peer side cannot watch the screen");
}
let mut c = create_capturer(captuerer_privacy_mode_id, display, encoder.use_yuv())?;
let capturer = create_capturer(captuerer_privacy_mode_id, display, use_yuv)?;
Ok(CapturerInfo {
origin,
width,
height,
ndisplay,
current,
privacy_mode_id,
_captuerer_privacy_mode_id: captuerer_privacy_mode_id,
capturer,
})
}
fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)]
ensure_close_virtual_device()?;
let mut c = get_capturer(true)?;
let mut video_qos = VIDEO_QOS.lock().unwrap();
video_qos.set_size(c.width as _, c.height as _);
let mut spf = video_qos.spf();
let bitrate = video_qos.generate_bitrate()?;
let abr = video_qos.check_abr_config();
drop(video_qos);
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
let encoder_cfg = match Encoder::current_hw_encoder_name() {
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
codec_name,
width: c.width,
height: c.height,
bitrate: bitrate as _,
}),
None => EncoderCfg::VPX(VpxEncoderConfig {
width: c.width as _,
height: c.height as _,
timebase: [1, 1000], // Output timestamp precision
bitrate,
codec: VpxVideoCodecId::VP9,
num_threads: (num_cpus::get() / 2) as _,
}),
};
let mut encoder;
match Encoder::new(encoder_cfg) {
Ok(x) => encoder = x,
Err(err) => bail!("Failed to create encoder: {}", err),
}
c.set_use_yuv(encoder.use_yuv());
if *SWITCH.lock().unwrap() {
log::debug!("Broadcasting display switch");
let mut misc = Misc::new();
misc.set_switch_display(SwitchDisplay {
display: current as _,
x: origin.0 as _,
y: origin.1 as _,
width: width as _,
height: height as _,
display: c.current as _,
x: c.origin.0 as _,
y: c.origin.1 as _,
width: c.width as _,
height: c.height as _,
..Default::default()
});
let mut msg_out = Message::new();
@@ -419,7 +486,7 @@ fn run(sp: GenericService) -> ResultType<()> {
while sp.ok() {
#[cfg(windows)]
check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?;
check_uac_switch(c.privacy_mode_id, c._captuerer_privacy_mode_id)?;
{
let mut video_qos = VIDEO_QOS.lock().unwrap();
@@ -437,11 +504,11 @@ fn run(sp: GenericService) -> ResultType<()> {
if *SWITCH.lock().unwrap() {
bail!("SWITCH");
}
if current != *CURRENT_DISPLAY.lock().unwrap() {
if c.current != *CURRENT_DISPLAY.lock().unwrap() {
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
}
check_privacy_mode_changed(&sp, privacy_mode_id)?;
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
#[cfg(windows)]
{
if crate::platform::windows::desktop_changed() {
@@ -451,7 +518,7 @@ fn run(sp: GenericService) -> ResultType<()> {
let now = time::Instant::now();
if last_check_displays.elapsed().as_millis() > 1000 {
last_check_displays = now;
if ndisplay != get_display_num() {
if c.ndisplay != get_display_num() {
log::info!("Displays changed");
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
@@ -515,7 +582,7 @@ fn run(sp: GenericService) -> ResultType<()> {
}
}
Err(err) => {
if check_display_changed(ndisplay, current, width, height) {
if check_display_changed(c.ndisplay, c.current, c.width, c.height) {
log::info!("Displays changed");
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
@@ -537,9 +604,9 @@ fn run(sp: GenericService) -> ResultType<()> {
let timeout_millis = 3_000u64;
let wait_begin = Instant::now();
while wait_begin.elapsed().as_millis() < timeout_millis as _ {
check_privacy_mode_changed(&sp, privacy_mode_id)?;
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
#[cfg(windows)]
check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?;
check_uac_switch(c.privacy_mode_id, c._captuerer_privacy_mode_id)?;
frame_controller.try_wait_next(&mut fetched_conn_ids, 300);
// break if all connections have received current frame
if fetched_conn_ids.len() >= frame_controller.send_conn_ids.len() {
@@ -633,6 +700,17 @@ pub fn handle_one_frame_encoded(
}
fn get_display_num() -> usize {
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
return if let Ok(n) = super::wayland::get_display_num() {
n
} else {
0
};
}
}
if let Ok(d) = try_get_displays() {
d.len()
} else {
@@ -640,14 +718,10 @@ fn get_display_num() -> usize {
}
}
pub fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
// switch to primary display if long time (30 seconds) no users
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {
*CURRENT_DISPLAY.lock().unwrap() = usize::MAX;
}
pub(super) fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
let mut displays = Vec::new();
let mut primary = 0;
for (i, d) in try_get_displays()?.iter().enumerate() {
for (i, d) in all.iter().enumerate() {
if d.is_primary() {
primary = i;
}
@@ -665,12 +739,26 @@ pub fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
if *lock >= displays.len() {
*lock = primary
}
Ok((*lock, displays))
(*lock, displays)
}
pub fn switch_display(i: i32) {
pub async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
return super::wayland::get_displays().await;
}
}
// switch to primary display if long time (30 seconds) no users
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {
*CURRENT_DISPLAY.lock().unwrap() = usize::MAX;
}
Ok(get_displays_2(&try_get_displays()?))
}
pub async fn switch_display(i: i32) {
let i = i as usize;
if let Ok((_, displays)) = get_displays() {
if let Ok((_, displays)) = get_displays().await {
if i < displays.len() {
*CURRENT_DISPLAY.lock().unwrap() = i;
}
@@ -684,6 +772,16 @@ pub fn refresh() {
}
fn get_primary() -> usize {
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
return match super::wayland::get_primary() {
Ok(n) => n,
Err(_) => 0,
};
}
}
if let Ok(all) = try_get_displays() {
for (i, d) in all.iter().enumerate() {
if d.is_primary() {
@@ -694,8 +792,8 @@ fn get_primary() -> usize {
0
}
pub fn switch_to_primary() {
switch_display(get_primary() as _);
pub async fn switch_to_primary() {
switch_display(get_primary() as _).await;
}
#[cfg(not(windows))]
@@ -733,16 +831,15 @@ fn try_get_displays() -> ResultType<Vec<Display>> {
Ok(displays)
}
fn get_current_display() -> ResultType<(usize, usize, Display)> {
pub(super) fn get_current_display_2(mut all: Vec<Display>) -> ResultType<(usize, usize, Display)> {
let mut current = *CURRENT_DISPLAY.lock().unwrap() as usize;
let mut displays = try_get_displays()?;
if displays.len() == 0 {
if all.len() == 0 {
bail!("No displays");
}
let n = displays.len();
let n = all.len();
if current >= n {
current = 0;
for (i, d) in displays.iter().enumerate() {
for (i, d) in all.iter().enumerate() {
if d.is_primary() {
current = i;
break;
@@ -750,5 +847,9 @@ fn get_current_display() -> ResultType<(usize, usize, Display)> {
}
*CURRENT_DISPLAY.lock().unwrap() = current;
}
return Ok((n, current, displays.remove(current)));
return Ok((n, current, all.remove(current)));
}
fn get_current_display() -> ResultType<(usize, usize, Display)> {
get_current_display_2(try_get_displays()?)
}

179
src/server/wayland.rs Normal file
View File

@@ -0,0 +1,179 @@
use super::*;
use hbb_common::allow_err;
use scrap::{Capturer, Display, Frame};
use std::{io::Result, time::Duration};
lazy_static::lazy_static! {
static ref CAP_DISPLAY_INFO: RwLock<u64> = RwLock::new(0);
}
struct CapDisplayInfo {
rects: Vec<((i32, i32), usize, usize)>,
displays: Vec<DisplayInfo>,
num: usize,
primary: usize,
current: usize,
capturer: *mut Capturer,
}
impl super::video_service::TraitCapturer for *mut Capturer {
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>> {
unsafe { (**self).frame(timeout) }
}
fn set_use_yuv(&mut self, use_yuv: bool) {
unsafe {
(**self).set_use_yuv(use_yuv);
}
}
}
async fn check_init() -> ResultType<()> {
if !scrap::is_x11() {
let mut minx = 0;
let mut maxx = 0;
let mut miny = 0;
let mut maxy = 0;
if *CAP_DISPLAY_INFO.read().unwrap() == 0 {
let mut lock = CAP_DISPLAY_INFO.write().unwrap();
if *lock == 0 {
let all = Display::all()?;
let num = all.len();
let (primary, displays) = super::video_service::get_displays_2(&all);
let mut rects: Vec<((i32, i32), usize, usize)> = Vec::new();
for d in &all {
rects.push((d.origin(), d.width(), d.height()));
}
let (ndisplay, current, display) =
super::video_service::get_current_display_2(all)?;
let (origin, width, height) = (display.origin(), display.width(), display.height());
log::debug!(
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}",
ndisplay,
current,
&origin,
width,
height,
num_cpus::get_physical(),
num_cpus::get(),
);
minx = origin.0;
maxx = origin.0 + width as i32;
miny = origin.1;
maxy = origin.1 + height as i32;
let capturer = Box::into_raw(Box::new(
Capturer::new(display, true).with_context(|| "Failed to create capturer")?,
));
let cap_display_info = Box::into_raw(Box::new(CapDisplayInfo {
rects,
displays,
num,
primary,
current,
capturer,
}));
*lock = cap_display_info as _;
}
}
if minx != maxx && miny != maxy {
log::info!(
"send uinput resolution: ({}, {}), ({}, {})",
minx,
maxx,
miny,
maxy
);
allow_err!(input_service::set_uinput_resolution(minx, maxx, miny, maxy).await);
allow_err!(input_service::set_uinput().await);
}
}
Ok(())
}
pub fn clear() {
if scrap::is_x11() {
return;
}
let mut lock = CAP_DISPLAY_INFO.write().unwrap();
if *lock != 0 {
unsafe {
let cap_display_info = Box::from_raw(*lock as *mut CapDisplayInfo);
let _ = Box::from_raw(cap_display_info.capturer);
}
*lock = 0;
}
}
pub(super) async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
check_init().await?;
let addr = *CAP_DISPLAY_INFO.read().unwrap();
if addr != 0 {
let cap_display_info: *const CapDisplayInfo = addr as _;
unsafe {
let cap_display_info = &*cap_display_info;
let primary = cap_display_info.primary;
let displays = cap_display_info.displays.clone();
Ok((primary, displays))
}
} else {
bail!("Failed to get capturer display info");
}
}
pub(super) fn get_primary() -> ResultType<usize> {
let addr = *CAP_DISPLAY_INFO.read().unwrap();
if addr != 0 {
let cap_display_info: *const CapDisplayInfo = addr as _;
unsafe {
let cap_display_info = &*cap_display_info;
Ok(cap_display_info.primary)
}
} else {
bail!("Failed to get capturer display info");
}
}
pub(super) fn get_display_num() -> ResultType<usize> {
let addr = *CAP_DISPLAY_INFO.read().unwrap();
if addr != 0 {
let cap_display_info: *const CapDisplayInfo = addr as _;
unsafe {
let cap_display_info = &*cap_display_info;
Ok(cap_display_info.num)
}
} else {
bail!("Failed to get capturer display info");
}
}
pub(super) fn get_capturer() -> ResultType<super::video_service::CapturerInfo> {
if scrap::is_x11() {
bail!("Do not call this function if not wayland");
}
let addr = *CAP_DISPLAY_INFO.read().unwrap();
if addr != 0 {
let cap_display_info: *const CapDisplayInfo = addr as _;
unsafe {
let cap_display_info = &*cap_display_info;
let rect = cap_display_info.rects[cap_display_info.current];
Ok(super::video_service::CapturerInfo {
origin: rect.0,
width: rect.1,
height: rect.2,
ndisplay: cap_display_info.num,
current: cap_display_info.current,
privacy_mode_id: 0,
_captuerer_privacy_mode_id: 0,
capturer: Box::new(cap_display_info.capturer),
})
}
} else {
bail!("Failed to get capturer display info");
}
}

117
src/ui.rs
View File

@@ -43,6 +43,7 @@ struct UI(
Arc<Mutex<HashMap<String, String>>>,
Arc<Mutex<String>>,
mpsc::UnboundedSender<ipc::Data>,
Arc<Mutex<String>>,
);
struct UIHostHandler;
@@ -169,7 +170,7 @@ pub fn start(args: &mut [String]) {
impl UI {
fn new(childs: Childs) -> Self {
let res = check_connect_status(true);
Self(childs, res.0, res.1, Default::default(), res.2)
Self(childs, res.0, res.1, Default::default(), res.2, res.3)
}
fn recent_sessions_updated(&mut self) -> bool {
@@ -186,16 +187,16 @@ impl UI {
ipc::get_id()
}
fn get_password(&mut self) -> String {
ipc::get_password()
fn get_random_password(&self) -> String {
ipc::get_random_password()
}
fn update_password(&mut self, password: String) {
if password.is_empty() {
allow_err!(ipc::set_password(Config::get_auto_password()));
} else {
allow_err!(ipc::set_password(password));
}
fn update_random_password(&self) {
allow_err!(ipc::set_random_password(Config::get_auto_password()));
}
fn set_security_password(&self, password: String) {
allow_err!(ipc::set_security_password(password));
}
fn get_remote_id(&mut self) -> String {
@@ -541,6 +542,16 @@ impl UI {
PeerConfig::remove(&id);
}
fn remove_discovered(&mut self, id: String) {
let mut peers = config::LanPeers::load().peers;
peers.retain(|x| x.id != id);
config::LanPeers::store(&peers);
}
fn send_wol(&mut self, id: String) {
crate::lan::send_wol(id)
}
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()];
@@ -685,16 +696,16 @@ impl UI {
fn discover(&self) {
std::thread::spawn(move || {
allow_err!(crate::rendezvous_mediator::discover());
allow_err!(crate::lan::discover());
});
}
fn get_lan_peers(&self) -> String {
config::LanPeers::load().peers
serde_json::to_string(&config::LanPeers::load().peers).unwrap_or_default()
}
fn get_uuid(&self) -> String {
base64::encode(crate::get_uuid())
base64::encode(hbb_common::get_uuid())
}
fn open_url(&self, url: String) {
@@ -764,6 +775,54 @@ impl UI {
fn get_langs(&self) -> String {
crate::lang::LANGS.to_string()
}
fn random_password_update_method(&self) -> String {
ipc::random_password_update_method()
}
fn set_random_password_update_method(&self, method: String) {
allow_err!(ipc::set_random_password_update_method(method));
}
fn is_random_password_enabled(&self) -> bool {
ipc::is_random_password_enabled()
}
fn set_random_password_enabled(&self, enabled: bool) {
allow_err!(ipc::set_random_password_enabled(enabled));
}
fn is_security_password_enabled(&self) -> bool {
ipc::is_security_password_enabled()
}
fn set_security_password_enabled(&self, enabled: bool) {
allow_err!(ipc::set_security_password_enabled(enabled));
}
fn is_onetime_password_enabled(&self) -> bool {
ipc::is_onetime_password_enabled()
}
fn set_onetime_password_enabled(&self, enabled: bool) {
allow_err!(ipc::set_onetime_password_enabled(enabled));
}
fn is_onetime_password_activated(&self) -> bool {
ipc::is_onetime_password_activated()
}
fn set_onetime_password_activated(&self, activated: bool) {
allow_err!(ipc::set_onetime_password_activated(activated));
}
fn is_random_password_valid(&self) -> bool {
ipc::is_random_password_valid()
}
fn password_description(&mut self) -> String {
self.5.lock().unwrap().clone()
}
}
impl sciter::EventHandler for UI {
@@ -773,14 +832,17 @@ impl sciter::EventHandler for UI {
fn is_xfce();
fn using_public_server();
fn get_id();
fn get_password();
fn update_password(String);
fn get_random_password();
fn update_random_password();
fn set_security_password(String);
fn get_remote_id();
fn set_remote_id(String);
fn closing(i32, i32, i32, i32);
fn get_size();
fn new_remote(String, bool);
fn send_wol(String);
fn remove_peer(String);
fn remove_discovered(String);
fn get_connect_status();
fn get_mouse_time();
fn check_mouse_time();
@@ -842,6 +904,18 @@ impl sciter::EventHandler for UI {
fn get_uuid();
fn has_hwcodec();
fn get_langs();
fn random_password_update_method();
fn set_random_password_update_method(String);
fn is_random_password_enabled();
fn set_random_password_enabled(bool);
fn is_security_password_enabled();
fn set_security_password_enabled(bool);
fn is_onetime_password_enabled();
fn set_onetime_password_enabled(bool);
fn is_onetime_password_activated();
fn set_onetime_password_activated(bool);
fn is_random_password_valid();
fn password_description();
}
}
@@ -881,6 +955,7 @@ async fn check_connect_status_(
status: Arc<Mutex<Status>>,
options: Arc<Mutex<HashMap<String, String>>>,
rx: mpsc::UnboundedReceiver<ipc::Data>,
password: Arc<Mutex<String>>,
) {
let mut key_confirmed = false;
let mut rx = rx;
@@ -907,6 +982,8 @@ async fn check_connect_status_(
Ok(Some(ipc::Data::Config((name, Some(value))))) => {
if name == "id" {
id = value;
} else if name == ipc::STR_PASSWORD_DESCRIPTION {
*password.lock().unwrap() = value;
}
}
Ok(Some(ipc::Data::OnlineStatus(Some((mut x, c))))) => {
@@ -926,6 +1003,7 @@ async fn check_connect_status_(
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((ipc::STR_PASSWORD_DESCRIPTION.to_owned(), None))).await.ok();
}
}
}
@@ -974,14 +1052,19 @@ fn check_connect_status(
Arc<Mutex<Status>>,
Arc<Mutex<HashMap<String, String>>>,
mpsc::UnboundedSender<ipc::Data>,
Arc<Mutex<String>>,
) {
let status = Arc::new(Mutex::new((0, false, 0, "".to_owned())));
let options = Arc::new(Mutex::new(Config::get_options()));
let cloned = status.clone();
let cloned_options = options.clone();
let (tx, rx) = mpsc::unbounded_channel::<ipc::Data>();
std::thread::spawn(move || check_connect_status_(reconnect, cloned, cloned_options, rx));
(status, options, tx)
let password = Arc::new(Mutex::new(String::default()));
let cloned_password = password.clone();
std::thread::spawn(move || {
check_connect_status_(reconnect, cloned, cloned_options, rx, cloned_password)
});
(status, options, tx, password)
}
const INVALID_FORMAT: &'static str = "Invalid format";
@@ -1045,7 +1128,7 @@ async fn check_id(
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::register_pk_response(rpr)) => {
Some(rendezvous_message::Union::RegisterPkResponse(rpr)) => {
match rpr.result.enum_value_or_default() {
register_pk_response::Result::OK => {
ok = true;

View File

@@ -318,9 +318,10 @@ class SessionList: Reactor.Component {
<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 #rdp>RDP<EditRdpPort /></li>
<li #wol>{translate('WOL')}</li>
<div .separator />
<li #rename>{translate('Rename')}</li>
{this.type != "fav" && this.type != "lan" && <li #remove>{translate('Remove')}</li>}
{this.type != "lan" && <li #rename>{translate('Rename')}</li>}
{this.type != "fav" && <li #remove>{translate('Remove')}</li>}
{is_win && <li #shortcut>{translate('Create Desktop Shortcut')}</li>}
<li #forget-password>{translate('Unremember Password')}</li>
{(!this.type || this.type == "fav") && <li #add-fav>{translate('Add to Favorites')}</li>}
@@ -419,6 +420,8 @@ class SessionList: Reactor.Component {
createNewConnect(id, "connect");
} else if (action == "transfer") {
createNewConnect(id, "file-transfer");
} else if (action == "wol") {
handler.send_wol(id);
} else if (action == "remove") {
if (this.type == "ab") {
for (var i = 0; i < ab.peers.length; ++i) {
@@ -429,6 +432,9 @@ class SessionList: Reactor.Component {
break;
}
}
} else if (this.type == "lan") {
handler.remove_discovered(id);
app.update();
} else {
handler.remove_peer(id);
app.update();

View File

@@ -204,7 +204,7 @@ impl ConnectionManager {
let mut req = FileTransferSendConfirmRequest {
id,
file_num,
union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)),
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
..Default::default()
};
let digest = FileTransferDigest {

View File

@@ -120,7 +120,7 @@ textarea:empty {
@ELLIPSIS;
}
div.password svg {
div.password svg:not(.checkmark) {
padding-left: 1em;
size: 16px;
color: #ddd;

View File

@@ -141,7 +141,7 @@ function adjustBorder() {
if (el) el.attributes.toggleClass("active", view.windowState == View.WINDOW_FULL_SCREEN);
}
var svg_checkmark = <svg viewBox="0 0 492 492"><path d="M484 105l-16-17a27 27 0 00-38 0L204 315 62 173c-5-5-12-7-19-7s-14 2-19 7L8 189a27 27 0 000 38l160 160v1l16 16c5 5 12 8 19 8 8 0 14-3 20-8l16-16v-1l245-244a27 27 0 000-38z"/></svg>;
var svg_checkmark = <svg class="checkmark" viewBox="0 0 492 492"><path d="M484 105l-16-17a27 27 0 00-38 0L204 315 62 173c-5-5-12-7-19-7s-14 2-19 7L8 189a27 27 0 000 38l160 160v1l16 16c5 5 12 8 19 8 8 0 14-3 20-8l16-16v-1l245-244a27 27 0 000-38z"/></svg>;
var svg_edit = <svg #edit viewBox="0 0 384 384">
<path d="M0 304v80h80l236-236-80-80zM378 56L328 6c-8-8-22-8-30 0l-39 39 80 80 39-39c8-8 8-22 0-30z"/>
</svg>;

View File

@@ -403,3 +403,18 @@ div.remote-session svg#menu {
background: none;
color: white;
}
svg#refresh-password {
display: inline-block;
stroke:#ddd;
}
svg#refresh-password:hover {
stroke:color(text);
}
li:disabled, li:disabled:hover {
color: color(lighter-text);
background: color(menu);
}

View File

@@ -20,6 +20,7 @@ var svg_menu = <svg #menu viewBox="0 0 512 512">
<circle cx="256" cy="448" r="64"/>
<circle cx="256" cy="64" r="64"/>
</svg>;
var svg_refresh_password = <svg #refresh-password xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 2v6h6M2.66 15.57a10 10 0 1 0 .57-8.38"/></svg>;
var my_id = "";
function get_id() {
@@ -520,10 +521,6 @@ class App: Reactor.Component
var is_can_screen_recording = handler.is_can_screen_recording(false);
return
<div .app>
<popup><menu.context #edit-password-context>
<li #refresh-password>{translate('Refresh random password')}</li>
<li #set-password>{translate('Set your own password')}</li>
</menu></popup>
<div .left-pane>
<div>
<div .title>{translate('Your Desktop')}</div>
@@ -533,8 +530,7 @@ class App: Reactor.Component
{key_confirmed ? <input type="text" readonly value={formatId(get_id())}/> : translate("Generating ...")}
</div>
<div .your-desktop>
<div>{translate('Password')}</div>
<Password />
<PasswordArea />
</div>
</div>
{!is_win || handler.is_installed() ? "": <InstallMe />}
@@ -806,44 +802,151 @@ function watch_screen_recording() {
class PasswordEyeArea : Reactor.Component {
render() {
var show = handler.is_random_password_valid();
var value = show ? handler.get_random_password() : "-";
return
<div .eye-area style="width: *">
<input|text @{this.input} readonly value="******" />
{svg_eye}
<input|text @{this.input} readonly value={value} />
{svg_refresh_password}
</div>;
}
event mouseenter {
var me = this;
me.leaved = false;
me.timer(300ms, function() {
if (me.leaved) return;
me.input.value = handler.get_password();
});
}
event mouseleave {
this.leaved = true;
this.input.value = "******";
event click $(svg#refresh-password) (_, me) {
if (handler.is_random_password_valid()) handler.update_random_password();
this.update();
}
}
class Password: Reactor.Component {
var verificationMethodMenu;
class VerificationMethodMenu: Reactor.Component {
function this() {
verificationMethodMenu = this;
}
function render() {
return <div .password style="flow:horizontal">
<PasswordEyeArea />
{svg_edit}
if (!this.show) return <li />;
var me = this;
self.timer(1ms, function() { me.toggleMenuState() });
return <li>{translate('Verification Method')}
<menu #verification-method>
<li #verification-method-security><span>{svg_checkmark}</span>{translate('Enable security password')}</li>
<li #verification-method-random><span>{svg_checkmark}</span>{translate('Enable random password')}</li>
</menu>
</li>;
}
function toggleMenuState() {
var security_enabled = handler.is_security_password_enabled();
var random_enabled = handler.is_random_password_enabled();
var onetime_enabled = handler.is_onetime_password_enabled();
for (var (index, el) in this.$$(menu#verification-method>li)) {
if (index == 0) el.attributes.toggleClass("selected", security_enabled);
if (index == 1) el.attributes.toggleClass("selected", random_enabled);
}
}
event click $(menu#verification-method>li) (_, me) {
switch (me.id.substring('verification-method-'.length)) {
case 'security':
{
var security_enabled = handler.is_security_password_enabled();
handler.set_security_password_enabled(!security_enabled);
}
break;
case 'random':
{
var random_enabled = handler.is_random_password_enabled();
handler.set_random_password_enabled(!random_enabled);
}
break;
}
this.toggleMenuState();
passwordArea.update();
}
}
var randomPasswordUpdateMethodMenu;
class RandomPasswordUpdateMethodMenu: Reactor.Component {
function this() {
randomPasswordUpdateMethodMenu = this;
}
function render() {
if (!this.show) return <li />;
var me = this;
var random_enabled = handler.is_random_password_enabled();
self.timer(1ms, function() { me.toggleMenuState() });
return <li disabled={ random_enabled ? "false" : "true" }>{translate('Random Password After Session')}
<menu #random-password-update-method>
<li #random-password-update-method-keep><span>{svg_checkmark}</span>{translate('Keep')}</li>
<li #random-password-update-method-update><span>{svg_checkmark}</span>{translate('Update')}</li>
<li #random-password-update-method-disable><span>{svg_checkmark}</span>{translate('Disable')}</li>
</menu>
</li>;
}
function toggleMenuState() {
var method = handler.random_password_update_method();
for (var (index, el) in this.$$(menu#random-password-update-method>li)) {
if (index == 0) el.attributes.toggleClass("selected", method == "KEEP");
if (index == 1) el.attributes.toggleClass("selected", method == "UPDATE");
if (index == 2) el.attributes.toggleClass("selected", method == "DISABLE");
}
}
event click $(menu#random-password-update-method>li) (_, me) {
if (me.id === 'random-password-update-method-keep') handler.set_random_password_update_method("KEEP");
if (me.id === 'random-password-update-method-update') handler.set_random_password_update_method("UPDATE");
if (me.id === 'random-password-update-method-disable') handler.set_random_password_update_method("DISABLE");
this.toggleMenuState();
passwordArea.update();
}
}
var passwordArea;
class PasswordArea: Reactor.Component {
function this() {
passwordArea = this;
}
function render() {
var onetime_enabled = handler.is_onetime_password_enabled();
return
<div>
<div>{translate(onetime_enabled ? 'Onetime Password' : 'Password')}</div>
<div .password style="flow:horizontal">
{this.renderPop()}
<PasswordEyeArea />
{svg_edit}
</div>
</div>;
}
event click $(svg#edit) (_, me) {
var menu = $(menu#edit-password-context);
me.popup(menu);
function renderPop() {
var security_enabled = handler.is_security_password_enabled();
var random_enabled = handler.is_random_password_enabled();
var onetime_enabled = handler.is_onetime_password_enabled();
var onetime_activated = handler.is_onetime_password_activated();
return <popup><menu.context #edit-password-context>
<li #enable-onetime-password disabled={ random_enabled ? "false" : "true" }>{translate(onetime_enabled ? "Disable onetime password" : "Enable onetime password")}</li>
<li #activate-onetime-password disabled={ !random_enabled || !onetime_enabled || onetime_activated ? "true" : "false" }>{translate('Activate onetime password')}</li>
<div .separator />
<VerificationMethodMenu />
<div .separator />
<li #set-password disabled={ security_enabled ? "false" : "true" }>{translate('Set security password')}</li>
<div .separator />
<RandomPasswordUpdateMethodMenu />
</menu></popup>;
}
event click $(li#refresh-password) {
handler.update_password("");
this.update();
event click $(svg#edit) (_, me) {
randomPasswordUpdateMethodMenu.update({show: true });
verificationMethodMenu.update({show: true });
var menu = $(menu#edit-password-context);
me.popup(menu);
}
event click $(li#set-password) {
@@ -862,12 +965,36 @@ class Password: Reactor.Component {
if (p0 != p1) {
return translate("The confirmation is not identical.");
}
handler.update_password(p0);
handler.set_security_password(p0);
me.update();
});
}
event click $(li#enable-onetime-password) {
var onetime_enabled = handler.is_onetime_password_enabled();
handler.set_onetime_password_enabled(!onetime_enabled);
passwordArea.update();
}
event click $(li#activate-onetime-password) {
handler.set_onetime_password_activated(true);
passwordArea.update();
}
}
var last_password_description = "";
function updatePasswordArea() {
self.timer(1s, function() {
var description = handler.password_description();
if (last_password_description != description) {
last_password_description = description
passwordArea.update();
}
updatePasswordArea();
});
}
updatePasswordArea();
class ID: Reactor.Component {
function render() {
return <input type="text" #remote_id .outline-focus novalue={translate("Enter Remote ID")} maxlength="21"

View File

@@ -258,10 +258,10 @@ pub fn check_main_window() {
let app = format!("/Applications/{}.app", crate::get_app_name());
let my_uid = sys
.process((std::process::id() as i32).into())
.map(|x| x.uid)
.map(|x| x.user_id())
.unwrap_or_default();
for (_, p) in sys.processes().iter() {
if p.cmd().len() == 1 && p.uid == my_uid && p.cmd()[0].contains(&app) {
if p.cmd().len() == 1 && p.user_id() == my_uid && p.cmd()[0].contains(&app) {
return;
}
}

View File

@@ -904,7 +904,7 @@ impl Handler {
fn get_char(&mut self, name: String, code: i32) -> String {
if let Some(key_event) = self.get_key_event(1, &name, code) {
match key_event.union {
Some(key_event::Union::chr(chr)) => {
Some(key_event::Union::Chr(chr)) => {
if let Some(chr) = std::char::from_u32(chr as _) {
return chr.to_string();
}
@@ -1763,6 +1763,11 @@ impl Remote {
// log::info!("new msg from ui, {}",data);
match data {
Data::Close => {
let mut misc = Misc::new();
misc.set_close_reason("".to_owned());
let mut msg = Message::new();
msg.set_misc(misc);
allow_err!(peer.send(&msg).await);
return false;
}
Data::Login((password, remember)) => {
@@ -1940,9 +1945,9 @@ impl Remote {
id,
file_num,
union: if need_override {
Some(file_transfer_send_confirm_request::Union::offset_blk(0))
Some(file_transfer_send_confirm_request::Union::OffsetBlk(0))
} else {
Some(file_transfer_send_confirm_request::Union::skip(true))
Some(file_transfer_send_confirm_request::Union::Skip(true))
},
..Default::default()
});
@@ -1958,9 +1963,9 @@ impl Remote {
id,
file_num,
union: if need_override {
Some(file_transfer_send_confirm_request::Union::offset_blk(0))
Some(file_transfer_send_confirm_request::Union::OffsetBlk(0))
} else {
Some(file_transfer_send_confirm_request::Union::skip(true))
Some(file_transfer_send_confirm_request::Union::Skip(true))
},
..Default::default()
});
@@ -2147,7 +2152,7 @@ impl Remote {
async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool {
if let Ok(msg_in) = Message::parse_from_bytes(&data) {
match msg_in.union {
Some(message::Union::video_frame(vf)) => {
Some(message::Union::VideoFrame(vf)) => {
if !self.first_frame {
self.first_frame = true;
self.handler.call2("closeSuccess", &make_args!());
@@ -2163,16 +2168,16 @@ impl Remote {
};
self.video_sender.send(MediaData::VideoFrame(vf)).ok();
}
Some(message::Union::hash(hash)) => {
Some(message::Union::Hash(hash)) => {
self.handler.handle_hash(hash, peer).await;
}
Some(message::Union::login_response(lr)) => match lr.union {
Some(login_response::Union::error(err)) => {
Some(message::Union::LoginResponse(lr)) => match lr.union {
Some(login_response::Union::Error(err)) => {
if !self.handler.handle_login_error(&err) {
return false;
}
}
Some(login_response::Union::peer_info(pi)) => {
Some(login_response::Union::PeerInfo(pi)) => {
self.handler.handle_peer_info(pi);
self.check_clipboard_file_context();
if !(self.handler.is_file_transfer()
@@ -2199,22 +2204,22 @@ impl Remote {
}
_ => {}
},
Some(message::Union::cursor_data(cd)) => {
Some(message::Union::CursorData(cd)) => {
self.handler.set_cursor_data(cd);
}
Some(message::Union::cursor_id(id)) => {
Some(message::Union::CursorId(id)) => {
self.handler.set_cursor_id(id.to_string());
}
Some(message::Union::cursor_position(cp)) => {
Some(message::Union::CursorPosition(cp)) => {
self.handler.set_cursor_position(cp);
}
Some(message::Union::clipboard(cb)) => {
Some(message::Union::Clipboard(cb)) => {
if !self.handler.lc.read().unwrap().disable_clipboard {
update_clipboard(cb, Some(&self.old_clipboard));
}
}
#[cfg(windows)]
Some(message::Union::cliprdr(clip)) => {
Some(message::Union::Cliprdr(clip)) => {
if !self.handler.lc.read().unwrap().disable_clipboard {
if let Some(context) = &mut self.clipboard_file_context {
if let Some(clip) = msg_2_clip(clip) {
@@ -2223,9 +2228,9 @@ impl Remote {
}
}
}
Some(message::Union::file_response(fr)) => {
Some(message::Union::FileResponse(fr)) => {
match fr.union {
Some(file_response::Union::dir(fd)) => {
Some(file_response::Union::Dir(fd)) => {
#[cfg(windows)]
let entries = fd.entries.to_vec();
#[cfg(not(windows))]
@@ -2248,7 +2253,7 @@ impl Remote {
job.files = entries;
}
}
Some(file_response::Union::digest(digest)) => {
Some(file_response::Union::Digest(digest)) => {
if digest.is_upload {
if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) {
if let Some(file) = job.files().get(digest.file_num as usize) {
@@ -2259,9 +2264,9 @@ impl Remote {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
file_transfer_send_confirm_request::Union::offset_blk(0)
file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else {
file_transfer_send_confirm_request::Union::skip(
file_transfer_send_confirm_request::Union::Skip(
true,
)
}),
@@ -2294,7 +2299,7 @@ impl Remote {
let msg= new_send_confirm(FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(file_transfer_send_confirm_request::Union::skip(true)),
union: Some(file_transfer_send_confirm_request::Union::Skip(true)),
..Default::default()
});
allow_err!(peer.send(&msg).await);
@@ -2306,9 +2311,9 @@ impl Remote {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
file_transfer_send_confirm_request::Union::offset_blk(0)
file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else {
file_transfer_send_confirm_request::Union::skip(true)
file_transfer_send_confirm_request::Union::Skip(true)
}),
..Default::default()
},
@@ -2331,7 +2336,7 @@ impl Remote {
FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)),
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
..Default::default()
},
);
@@ -2346,7 +2351,7 @@ impl Remote {
}
}
}
Some(file_response::Union::block(block)) => {
Some(file_response::Union::Block(block)) => {
log::info!(
"file response block, file id:{}, file num: {}",
block.id,
@@ -2359,27 +2364,27 @@ impl Remote {
self.update_jobs_status();
}
}
Some(file_response::Union::done(d)) => {
Some(file_response::Union::Done(d)) => {
if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) {
job.modify_time();
fs::remove_job(d.id, &mut self.write_jobs);
}
self.handle_job_status(d.id, d.file_num, None);
}
Some(file_response::Union::error(e)) => {
Some(file_response::Union::Error(e)) => {
self.handle_job_status(e.id, e.file_num, Some(e.error));
}
_ => {}
}
}
Some(message::Union::misc(misc)) => match misc.union {
Some(misc::Union::audio_format(f)) => {
Some(message::Union::Misc(misc)) => match misc.union {
Some(misc::Union::AudioFormat(f)) => {
self.audio_sender.send(MediaData::AudioFormat(f)).ok();
}
Some(misc::Union::chat_message(c)) => {
Some(misc::Union::ChatMessage(c)) => {
self.handler.call("newMessage", &make_args!(c.text));
}
Some(misc::Union::permission_info(p)) => {
Some(misc::Union::PermissionInfo(p)) => {
log::info!("Change permission {:?} -> {}", p.permission, p.enabled);
match p.permission.enum_value_or_default() {
Permission::Keyboard => {
@@ -2407,7 +2412,7 @@ impl Remote {
}
}
}
Some(misc::Union::switch_display(s)) => {
Some(misc::Union::SwitchDisplay(s)) => {
self.handler.call("switchDisplay", &make_args!(s.display));
self.video_sender.send(MediaData::Reset).ok();
if s.width > 0 && s.height > 0 {
@@ -2423,27 +2428,27 @@ impl Remote {
self.handler.set_display(s.x, s.y, s.width, s.height);
}
}
Some(misc::Union::close_reason(c)) => {
Some(misc::Union::CloseReason(c)) => {
self.handler.msgbox("error", "Connection Error", &c);
return false;
}
Some(misc::Union::back_notification(notification)) => {
Some(misc::Union::BackNotification(notification)) => {
if !self.handle_back_notification(notification).await {
return false;
}
}
_ => {}
},
Some(message::Union::test_delay(t)) => {
Some(message::Union::TestDelay(t)) => {
self.handler.handle_test_delay(t, peer).await;
}
Some(message::Union::audio_frame(frame)) => {
Some(message::Union::AudioFrame(frame)) => {
if !self.handler.lc.read().unwrap().disable_audio {
self.audio_sender.send(MediaData::AudioFrame(frame)).ok();
}
}
Some(message::Union::file_action(action)) => match action.union {
Some(file_action::Union::send_confirm(c)) => {
Some(message::Union::FileAction(action)) => match action.union {
Some(file_action::Union::SendConfirm(c)) => {
if let Some(job) = fs::get_job(c.id, &mut self.read_jobs) {
job.confirm(&c);
}
@@ -2458,13 +2463,13 @@ impl Remote {
async fn handle_back_notification(&mut self, notification: BackNotification) -> bool {
match notification.union {
Some(back_notification::Union::block_input_state(state)) => {
Some(back_notification::Union::BlockInputState(state)) => {
self.handle_back_msg_block_input(
state.enum_value_or(back_notification::BlockInputState::StateUnknown),
)
.await;
}
Some(back_notification::Union::privacy_mode_state(state)) => {
Some(back_notification::Union::PrivacyModeState(state)) => {
if !self
.handle_back_msg_privacy_mode(
state.enum_value_or(back_notification::PrivacyModeState::StateUnknown),