mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge remote-tracking branch 'rustdesk/master' into flutter_desktop
# Conflicts: # .github/workflows/ci.yml # Cargo.lock # Cargo.toml # flutter/lib/common.dart # flutter/lib/mobile/pages/remote_page.dart # flutter/lib/mobile/pages/server_page.dart # flutter/lib/mobile/pages/settings_page.dart # flutter/lib/mobile/widgets/dialog.dart # flutter/lib/models/model.dart # flutter/lib/models/server_model.dart # src/client.rs # src/common.rs # src/ipc.rs # src/mobile_ffi.rs # src/rendezvous_mediator.rs # src/ui.rs
This commit is contained in:
@@ -347,7 +347,7 @@ fn send_f32(data: &[f32], encoder: &mut Encoder, sp: &GenericService) {
|
||||
Ok(data) => {
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_audio_frame(AudioFrame {
|
||||
data,
|
||||
data: data.into(),
|
||||
timestamp: crate::common::get_time(),
|
||||
..Default::default()
|
||||
});
|
||||
@@ -367,7 +367,7 @@ fn send_f32(data: &[f32], encoder: &mut Encoder, sp: &GenericService) {
|
||||
Ok(data) => {
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_audio_frame(AudioFrame {
|
||||
data,
|
||||
data: data.into(),
|
||||
timestamp: crate::common::get_time(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -3,17 +3,18 @@ use super::{input_service::*, *};
|
||||
use crate::clipboard_file::*;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::common::update_clipboard;
|
||||
use crate::video_service;
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
use crate::{common::MOBILE_INFO2, flutter::connection_manager::start_channel};
|
||||
use crate::{ipc, VERSION};
|
||||
use hbb_common::fs::can_enable_overwrite_detection;
|
||||
use hbb_common::{
|
||||
config::Config,
|
||||
fs,
|
||||
fs::can_enable_overwrite_detection,
|
||||
futures::{SinkExt, StreamExt},
|
||||
get_version_number,
|
||||
message_proto::{option_message::BoolOption, permission_info::Permission},
|
||||
sleep, timeout,
|
||||
password_security as password, sleep, timeout,
|
||||
tokio::{
|
||||
net::TcpStream,
|
||||
sync::mpsc,
|
||||
@@ -29,11 +30,14 @@ use std::sync::{
|
||||
atomic::{AtomicI64, Ordering},
|
||||
mpsc as std_mpsc,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use system_shutdown;
|
||||
|
||||
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);
|
||||
@@ -52,6 +56,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,
|
||||
@@ -68,8 +80,8 @@ pub struct Connection {
|
||||
clipboard: bool,
|
||||
audio: bool,
|
||||
file: bool,
|
||||
restart: bool,
|
||||
last_test_delay: i64,
|
||||
image_quality: i32,
|
||||
lock_after_session_end: bool,
|
||||
show_remote_cursor: bool, // by peer
|
||||
ip: String,
|
||||
@@ -80,6 +92,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 +105,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)));
|
||||
});
|
||||
@@ -105,12 +119,13 @@ impl Subscriber for ConnInner {
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_DELAY_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
const TEST_DELAY_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
const SEC30: Duration = Duration::from_secs(30);
|
||||
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(
|
||||
@@ -121,7 +136,7 @@ impl Connection {
|
||||
) {
|
||||
let hash = Hash {
|
||||
salt: Config::get_salt(),
|
||||
challenge: Config::get_auto_password(),
|
||||
challenge: Config::get_auto_password(6),
|
||||
..Default::default()
|
||||
};
|
||||
let (tx_from_cm_holder, mut rx_from_cm) = mpsc::unbounded_channel::<ipc::Data>();
|
||||
@@ -153,8 +168,8 @@ impl Connection {
|
||||
clipboard: Config::get_option("enable-clipboard").is_empty(),
|
||||
audio: Config::get_option("enable-audio").is_empty(),
|
||||
file: Config::get_option("enable-file-transfer").is_empty(),
|
||||
restart: Config::get_option("enable-remote-restart").is_empty(),
|
||||
last_test_delay: 0,
|
||||
image_quality: ImageQuality::Balanced.value(),
|
||||
lock_after_session_end: false,
|
||||
show_remote_cursor: false,
|
||||
ip: "".to_owned(),
|
||||
@@ -165,6 +180,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 {
|
||||
@@ -190,6 +207,9 @@ impl Connection {
|
||||
if !conn.file {
|
||||
conn.send_permission(Permission::File, false).await;
|
||||
}
|
||||
if !conn.restart {
|
||||
conn.send_permission(Permission::Restart, false).await;
|
||||
}
|
||||
let mut test_delay_timer =
|
||||
time::interval_at(Instant::now() + TEST_DELAY_TIMEOUT, TEST_DELAY_TIMEOUT);
|
||||
let mut last_recv_time = Instant::now();
|
||||
@@ -223,7 +243,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} => {
|
||||
@@ -266,6 +287,9 @@ impl Connection {
|
||||
conn.file = enabled;
|
||||
conn.send_permission(Permission::File, enabled).await;
|
||||
conn.send_to_cm(ipc::Data::ClipboardFileEnabled(conn.file_transfer_enabled()));
|
||||
} else if &name == "restart" {
|
||||
conn.restart = enabled;
|
||||
conn.send_permission(Permission::Restart, enabled).await;
|
||||
}
|
||||
}
|
||||
ipc::Data::RawMessage(bytes) => {
|
||||
@@ -282,24 +306,24 @@ impl Connection {
|
||||
ipc::PrivacyModeState::OffSucceeded => {
|
||||
video_service::set_privacy_mode_conn_id(0);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OffSucceeded,
|
||||
back_notification::PrivacyModeState::PrvOffSucceeded,
|
||||
)
|
||||
}
|
||||
ipc::PrivacyModeState::OffFailed => {
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OffFailed,
|
||||
back_notification::PrivacyModeState::PrvOffFailed,
|
||||
)
|
||||
}
|
||||
ipc::PrivacyModeState::OffByPeer => {
|
||||
video_service::set_privacy_mode_conn_id(0);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OffByPeer,
|
||||
back_notification::PrivacyModeState::PrvOffByPeer,
|
||||
)
|
||||
}
|
||||
ipc::PrivacyModeState::OffUnknown => {
|
||||
video_service::set_privacy_mode_conn_id(0);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OffUnknown,
|
||||
back_notification::PrivacyModeState::PrvOffUnknown,
|
||||
)
|
||||
}
|
||||
};
|
||||
@@ -312,11 +336,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;
|
||||
@@ -325,14 +350,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 {
|
||||
@@ -345,7 +370,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;
|
||||
}
|
||||
},
|
||||
@@ -355,7 +380,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;
|
||||
}
|
||||
@@ -363,21 +388,24 @@ 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();
|
||||
if time > 0 && conn.last_test_delay == 0 {
|
||||
conn.last_test_delay = time;
|
||||
let mut msg_out = Message::new();
|
||||
let qos = video_service::VIDEO_QOS.lock().unwrap();
|
||||
msg_out.set_test_delay(TestDelay{
|
||||
time,
|
||||
last_delay:qos.current_delay,
|
||||
target_bitrate:qos.target_bitrate,
|
||||
..Default::default()
|
||||
});
|
||||
conn.inner.send(msg_out.into());
|
||||
@@ -394,10 +422,13 @@ impl Connection {
|
||||
let _ = privacy_mode::turn_off_privacy(0);
|
||||
}
|
||||
video_service::notify_video_frame_feched(id, None);
|
||||
video_service::update_test_latency(id, 0);
|
||||
video_service::update_image_quality(id, None);
|
||||
scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove);
|
||||
video_service::VIDEO_QOS.lock().unwrap().reset();
|
||||
if conn.authorized {
|
||||
password::update_temporary_password();
|
||||
}
|
||||
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!({
|
||||
@@ -432,7 +463,7 @@ impl Connection {
|
||||
} else {
|
||||
Self::send_block_input_error(
|
||||
&tx,
|
||||
back_notification::BlockInputState::OnFailed,
|
||||
back_notification::BlockInputState::BlkOnFailed,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -442,7 +473,7 @@ impl Connection {
|
||||
} else {
|
||||
Self::send_block_input_error(
|
||||
&tx,
|
||||
back_notification::BlockInputState::OffFailed,
|
||||
back_notification::BlockInputState::BlkOffFailed,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -490,7 +521,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");
|
||||
}
|
||||
@@ -569,7 +600,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);
|
||||
@@ -615,6 +646,16 @@ impl Connection {
|
||||
pi.hostname = MOBILE_INFO2.lock().unwrap().clone();
|
||||
pi.platform = "Android".into();
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
{
|
||||
let (h264, h265) = scrap::codec::Encoder::supported_encoding();
|
||||
pi.encoding = Some(SupportedEncoding {
|
||||
h264,
|
||||
h265,
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
}
|
||||
|
||||
if self.port_forward_socket.is_some() {
|
||||
let mut msg_out = Message::new();
|
||||
@@ -626,9 +667,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();
|
||||
@@ -664,7 +705,7 @@ impl Connection {
|
||||
res.set_peer_info(pi);
|
||||
} else {
|
||||
try_activate_screen();
|
||||
match super::video_service::get_displays() {
|
||||
match super::video_service::get_displays().await {
|
||||
Err(err) => {
|
||||
res.set_error(format!("X11 error: {}", err));
|
||||
}
|
||||
@@ -734,6 +775,7 @@ impl Connection {
|
||||
audio: self.audio,
|
||||
file: self.file,
|
||||
file_transfer_enabled: self.file_transfer_enabled(),
|
||||
restart: self.restart,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -776,17 +818,99 @@ 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::temporary_enabled() {
|
||||
let password = password::temporary_password();
|
||||
if self.validate_one_password(password.clone()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if password::permanent_enabled() {
|
||||
if self.validate_one_password(Config::get_permanent_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() {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::State(q),
|
||||
);
|
||||
} else {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||
);
|
||||
}
|
||||
self.video_ack_required = lr.video_ack_required;
|
||||
if self.authorized {
|
||||
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;
|
||||
@@ -795,7 +919,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;
|
||||
@@ -832,15 +956,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::has_valid_password() {
|
||||
self.send_login_error("Connection not allowed").await;
|
||||
return false;
|
||||
}
|
||||
let mut failure = LOGIN_FAILURES
|
||||
.lock()
|
||||
.unwrap()
|
||||
@@ -853,7 +981,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;
|
||||
@@ -879,21 +1007,22 @@ 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);
|
||||
self.inner.send(msg_out.into());
|
||||
} else {
|
||||
self.last_test_delay = 0;
|
||||
let latency = crate::get_time() - t.time;
|
||||
if latency > 0 {
|
||||
super::video_service::update_test_latency(self.inner.id(), latency);
|
||||
}
|
||||
let new_delay = (crate::get_time() - t.time) as u32;
|
||||
video_service::VIDEO_QOS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update_network_delay(new_delay);
|
||||
}
|
||||
} 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);
|
||||
@@ -908,7 +1037,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) {
|
||||
@@ -924,8 +1053,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);
|
||||
}
|
||||
_ => {
|
||||
@@ -937,14 +1066,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) {
|
||||
@@ -952,13 +1081,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;
|
||||
@@ -968,7 +1097,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));
|
||||
@@ -993,7 +1122,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,
|
||||
@@ -1006,31 +1135,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);
|
||||
}
|
||||
@@ -1039,8 +1168,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,
|
||||
@@ -1048,13 +1177,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,
|
||||
@@ -1063,27 +1192,43 @@ impl Connection {
|
||||
}),
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::misc(misc)) => match misc.union {
|
||||
Some(misc::Union::switch_display(s)) => {
|
||||
super::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 {
|
||||
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;
|
||||
}
|
||||
|
||||
Some(misc::Union::RestartRemoteDevice(_)) =>
|
||||
{
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if self.restart {
|
||||
match system_shutdown::reboot() {
|
||||
Ok(_) => log::info!("Restart by the peer"),
|
||||
Err(e) => log::error!("Failed to restart:{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
@@ -1095,13 +1240,20 @@ impl Connection {
|
||||
async fn update_option(&mut self, o: &OptionMessage) {
|
||||
log::info!("Option update: {:?}", o);
|
||||
if let Ok(q) = o.image_quality.enum_value() {
|
||||
self.image_quality = q.value();
|
||||
super::video_service::update_image_quality(self.inner.id(), Some(q.value()));
|
||||
}
|
||||
let q = o.custom_image_quality;
|
||||
if q > 0 {
|
||||
self.image_quality = q;
|
||||
super::video_service::update_image_quality(self.inner.id(), Some(q));
|
||||
let image_quality;
|
||||
if let ImageQuality::NotSet = q {
|
||||
if o.custom_image_quality > 0 {
|
||||
image_quality = o.custom_image_quality;
|
||||
} else {
|
||||
image_quality = ImageQuality::Balanced.value();
|
||||
}
|
||||
} else {
|
||||
image_quality = q.value();
|
||||
}
|
||||
video_service::VIDEO_QOS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update_image_quality(image_quality);
|
||||
}
|
||||
if let Ok(q) = o.lock_after_session_end.enum_value() {
|
||||
if q != BoolOption::NotSet {
|
||||
@@ -1164,7 +1316,7 @@ impl Connection {
|
||||
BoolOption::Yes => {
|
||||
let msg_out = if !video_service::is_privacy_mode_supported() {
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::NotSupported,
|
||||
back_notification::PrivacyModeState::PrvNotSupported,
|
||||
)
|
||||
} else {
|
||||
match privacy_mode::turn_on_privacy(self.inner.id) {
|
||||
@@ -1172,7 +1324,7 @@ impl Connection {
|
||||
if video_service::test_create_capturer(self.inner.id, 5_000) {
|
||||
video_service::set_privacy_mode_conn_id(self.inner.id);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OnSucceeded,
|
||||
back_notification::PrivacyModeState::PrvOnSucceeded,
|
||||
)
|
||||
} else {
|
||||
log::error!(
|
||||
@@ -1181,12 +1333,12 @@ impl Connection {
|
||||
video_service::set_privacy_mode_conn_id(0);
|
||||
let _ = privacy_mode::turn_off_privacy(self.inner.id);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OnFailed,
|
||||
back_notification::PrivacyModeState::PrvOnFailed,
|
||||
)
|
||||
}
|
||||
}
|
||||
Ok(false) => crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OnFailedPlugin,
|
||||
back_notification::PrivacyModeState::PrvOnFailedPlugin,
|
||||
),
|
||||
Err(e) => {
|
||||
log::error!("Failed to turn on privacy mode. {}", e);
|
||||
@@ -1194,7 +1346,7 @@ impl Connection {
|
||||
let _ = privacy_mode::turn_off_privacy(0);
|
||||
}
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OnFailed,
|
||||
back_notification::PrivacyModeState::PrvOnFailed,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1204,7 +1356,7 @@ impl Connection {
|
||||
BoolOption::No => {
|
||||
let msg_out = if !video_service::is_privacy_mode_supported() {
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::NotSupported,
|
||||
back_notification::PrivacyModeState::PrvNotSupported,
|
||||
)
|
||||
} else {
|
||||
video_service::set_privacy_mode_conn_id(0);
|
||||
@@ -1229,16 +1381,22 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(q) = o.video_codec_state.clone().take() {
|
||||
scrap::codec::Encoder::update_video_encoder(
|
||||
self.inner.id(),
|
||||
scrap::codec::EncoderUpdate::State(q),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -1337,7 +1495,7 @@ async fn start_ipc(
|
||||
file_num,
|
||||
data,
|
||||
compressed}) = data {
|
||||
stream.send(&Data::FS(ipc::FS::WriteBlock{id, file_num, data: Vec::new(), compressed})).await?;
|
||||
stream.send(&Data::FS(ipc::FS::WriteBlock{id, file_num, data: Bytes::new(), compressed})).await?;
|
||||
stream.send_raw(data).await?;
|
||||
} else {
|
||||
stream.send(&data).await?;
|
||||
@@ -1373,19 +1531,19 @@ mod privacy_mode {
|
||||
let res = turn_off_privacy(_conn_id, None);
|
||||
match res {
|
||||
Ok(_) => crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OffSucceeded,
|
||||
back_notification::PrivacyModeState::PrvOffSucceeded,
|
||||
),
|
||||
Err(e) => {
|
||||
log::error!("Failed to turn off privacy mode {}", e);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OffFailed,
|
||||
back_notification::PrivacyModeState::PrvOffFailed,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
crate::common::make_privacy_mode_msg(back_notification::PrivacyModeState::OffFailed)
|
||||
crate::common::make_privacy_mode_msg(back_notification::PrivacyModeState::PrvOffFailed)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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::EnumOrUnknown};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
@@ -68,7 +68,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 {
|
||||
@@ -145,7 +145,7 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
|
||||
msg = cached.clone();
|
||||
} else {
|
||||
let mut data = crate::get_cursor_data(hcursor)?;
|
||||
data.colors = hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL);
|
||||
data.colors = hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into();
|
||||
let mut tmp = Message::new();
|
||||
tmp.set_cursor_data(data);
|
||||
msg = Arc::new(tmp);
|
||||
@@ -187,6 +187,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;
|
||||
@@ -299,12 +319,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
|
||||
@@ -315,7 +335,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);
|
||||
}
|
||||
@@ -430,7 +450,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;
|
||||
@@ -439,7 +459,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
|
||||
@@ -469,7 +489,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! {
|
||||
@@ -548,7 +568,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();
|
||||
@@ -598,7 +617,7 @@ fn handle_key_(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
|
||||
@@ -653,7 +672,7 @@ fn handle_key_(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()) {
|
||||
@@ -679,10 +698,10 @@ fn handle_key_(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
|
||||
@@ -708,12 +727,12 @@ fn handle_key_(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);
|
||||
}
|
||||
_ => {}
|
||||
@@ -729,6 +748,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
651
src/server/uinput.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
218
src/server/video_qos.rs
Normal file
218
src/server/video_qos.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
const FPS: u8 = 30;
|
||||
trait Percent {
|
||||
fn as_percent(&self) -> u32;
|
||||
}
|
||||
|
||||
impl Percent for ImageQuality {
|
||||
fn as_percent(&self) -> u32 {
|
||||
match self {
|
||||
ImageQuality::NotSet => 0,
|
||||
ImageQuality::Low => 50,
|
||||
ImageQuality::Balanced => 66,
|
||||
ImageQuality::Best => 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VideoQoS {
|
||||
width: u32,
|
||||
height: u32,
|
||||
user_image_quality: u32,
|
||||
current_image_quality: u32,
|
||||
enable_abr: bool,
|
||||
pub current_delay: u32,
|
||||
pub fps: u8, // abr
|
||||
pub target_bitrate: u32, // abr
|
||||
updated: bool,
|
||||
state: DelayState,
|
||||
debounce_count: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum DelayState {
|
||||
Normal = 0,
|
||||
LowDelay = 200,
|
||||
HighDelay = 500,
|
||||
Broken = 1000,
|
||||
}
|
||||
|
||||
impl DelayState {
|
||||
fn from_delay(delay: u32) -> Self {
|
||||
if delay > DelayState::Broken as u32 {
|
||||
DelayState::Broken
|
||||
} else if delay > DelayState::HighDelay as u32 {
|
||||
DelayState::HighDelay
|
||||
} else if delay > DelayState::LowDelay as u32 {
|
||||
DelayState::LowDelay
|
||||
} else {
|
||||
DelayState::Normal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VideoQoS {
|
||||
fn default() -> Self {
|
||||
VideoQoS {
|
||||
fps: FPS,
|
||||
user_image_quality: ImageQuality::Balanced.as_percent(),
|
||||
current_image_quality: ImageQuality::Balanced.as_percent(),
|
||||
enable_abr: false,
|
||||
width: 0,
|
||||
height: 0,
|
||||
current_delay: 0,
|
||||
target_bitrate: 0,
|
||||
updated: false,
|
||||
state: DelayState::Normal,
|
||||
debounce_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoQoS {
|
||||
pub fn set_size(&mut self, width: u32, height: u32) {
|
||||
if width == 0 || height == 0 {
|
||||
return;
|
||||
}
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
}
|
||||
|
||||
pub fn spf(&mut self) -> Duration {
|
||||
if self.fps <= 0 {
|
||||
self.fps = FPS;
|
||||
}
|
||||
Duration::from_secs_f32(1. / (self.fps as f32))
|
||||
}
|
||||
|
||||
// update_network_delay periodically
|
||||
// decrease the bitrate when the delay gets bigger
|
||||
pub fn update_network_delay(&mut self, delay: u32) {
|
||||
if self.current_delay.eq(&0) {
|
||||
self.current_delay = delay;
|
||||
return;
|
||||
}
|
||||
|
||||
self.current_delay = delay / 2 + self.current_delay / 2;
|
||||
log::trace!(
|
||||
"VideoQoS update_network_delay:{}, {}, state:{:?}",
|
||||
self.current_delay,
|
||||
delay,
|
||||
self.state,
|
||||
);
|
||||
|
||||
// ABR
|
||||
if !self.enable_abr {
|
||||
return;
|
||||
}
|
||||
let current_state = DelayState::from_delay(self.current_delay);
|
||||
if current_state != self.state && self.debounce_count > 5 {
|
||||
log::debug!(
|
||||
"VideoQoS state changed:{:?} -> {:?}",
|
||||
self.state,
|
||||
current_state
|
||||
);
|
||||
self.state = current_state;
|
||||
self.debounce_count = 0;
|
||||
self.refresh_quality();
|
||||
} else {
|
||||
self.debounce_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_quality(&mut self) {
|
||||
match self.state {
|
||||
DelayState::Normal => {
|
||||
self.fps = FPS;
|
||||
self.current_image_quality = self.user_image_quality;
|
||||
}
|
||||
DelayState::LowDelay => {
|
||||
self.fps = FPS;
|
||||
self.current_image_quality = std::cmp::min(self.user_image_quality, 50);
|
||||
}
|
||||
DelayState::HighDelay => {
|
||||
self.fps = FPS / 2;
|
||||
self.current_image_quality = std::cmp::min(self.user_image_quality, 25);
|
||||
}
|
||||
DelayState::Broken => {
|
||||
self.fps = FPS / 4;
|
||||
self.current_image_quality = 10;
|
||||
}
|
||||
}
|
||||
let _ = self.generate_bitrate().ok();
|
||||
self.updated = true;
|
||||
}
|
||||
|
||||
// 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 _;
|
||||
if self.current_image_quality != image_quality {
|
||||
self.current_image_quality = image_quality;
|
||||
let _ = self.generate_bitrate().ok();
|
||||
self.updated = true;
|
||||
}
|
||||
|
||||
self.user_image_quality = self.current_image_quality;
|
||||
}
|
||||
|
||||
pub fn generate_bitrate(&mut self) -> ResultType<u32> {
|
||||
// https://www.nvidia.com/en-us/geforce/guides/broadcasting-guide/
|
||||
if self.width == 0 || self.height == 0 {
|
||||
bail!("Fail to generate_bitrate, width or height is not set");
|
||||
}
|
||||
if self.current_image_quality == 0 {
|
||||
self.current_image_quality = ImageQuality::Balanced.as_percent();
|
||||
}
|
||||
|
||||
let base_bitrate = ((self.width * self.height) / 800) as u32;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
// fix when andorid screen shrinks
|
||||
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;
|
||||
Ok(self.target_bitrate)
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
self.target_bitrate = base_bitrate * self.current_image_quality / 100;
|
||||
Ok(self.target_bitrate)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_if_updated(&mut self) -> bool {
|
||||
if self.updated {
|
||||
self.updated = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
*self = Default::default();
|
||||
}
|
||||
|
||||
pub fn check_abr_config(&mut self) -> bool {
|
||||
self.enable_abr = if let Some(v) = Config2::get().options.get("enable-abr") {
|
||||
v != "N"
|
||||
} else {
|
||||
true // default is true
|
||||
};
|
||||
self.enable_abr
|
||||
}
|
||||
|
||||
pub fn convert_quality(q: i32) -> i32 {
|
||||
if q == ImageQuality::Balanced.value() {
|
||||
100 * 2 / 3
|
||||
} else if q == ImageQuality::Low.value() {
|
||||
100 / 2
|
||||
} else if q == ImageQuality::Best.value() {
|
||||
100
|
||||
} else {
|
||||
(q >> 8 & 0xFF) * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,20 @@
|
||||
// to-do:
|
||||
// https://slhck.info/video/2017/03/01/rate-control.html
|
||||
|
||||
use super::*;
|
||||
use super::{video_qos::VideoQoS, *};
|
||||
use hbb_common::tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
Mutex as TokioMutex,
|
||||
};
|
||||
use scrap::{Capturer, Config, Display, EncodeFrame, Encoder, Frame, VideoCodecId, STRIDE_ALIGN};
|
||||
use scrap::{
|
||||
codec::{Encoder, EncoderCfg, HwEncoderConfig},
|
||||
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
|
||||
Capturer, Display, TraitCapturer,
|
||||
};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
io::{ErrorKind::WouldBlock, Result},
|
||||
io::ErrorKind::WouldBlock,
|
||||
ops::{Deref, DerefMut},
|
||||
time::{self, Duration, Instant},
|
||||
};
|
||||
#[cfg(windows)]
|
||||
@@ -38,14 +43,13 @@ lazy_static::lazy_static! {
|
||||
static ref CURRENT_DISPLAY: Arc<Mutex<usize>> = Arc::new(Mutex::new(usize::MAX));
|
||||
static ref LAST_ACTIVE: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
|
||||
static ref SWITCH: Arc<Mutex<bool>> = Default::default();
|
||||
static ref TEST_LATENCIES: Arc<Mutex<HashMap<i32, i64>>> = Default::default();
|
||||
static ref IMAGE_QUALITIES: Arc<Mutex<HashMap<i32, i32>>> = Default::default();
|
||||
static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option<Instant>)>, Arc<TokioMutex<UnboundedReceiver<(i32, Option<Instant>)>>>) = {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
(tx, Arc::new(TokioMutex::new(rx)))
|
||||
};
|
||||
static ref PRIVACY_MODE_CONN_ID: Mutex<i32> = Mutex::new(0);
|
||||
static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported();
|
||||
pub static ref VIDEO_QOS: Arc<Mutex<VideoQoS>> = Default::default();
|
||||
}
|
||||
|
||||
fn is_capturer_mag_supported() -> bool {
|
||||
@@ -124,46 +128,6 @@ impl VideoFrameController {
|
||||
}
|
||||
}
|
||||
|
||||
trait TraitCapturer {
|
||||
fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result<Frame<'a>>;
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_gdi(&self) -> bool;
|
||||
#[cfg(windows)]
|
||||
fn set_gdi(&mut self) -> bool;
|
||||
}
|
||||
|
||||
impl TraitCapturer for Capturer {
|
||||
fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result<Frame<'a>> {
|
||||
self.frame(timeout_ms)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_gdi(&self) -> bool {
|
||||
self.is_gdi()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn set_gdi(&mut self) -> bool {
|
||||
self.set_gdi()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl TraitCapturer for scrap::CapturerMag {
|
||||
fn frame<'a>(&'a mut self, _timeout_ms: u32) -> Result<Frame<'a>> {
|
||||
self.frame(_timeout_ms)
|
||||
}
|
||||
|
||||
fn is_gdi(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_gdi(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> GenericService {
|
||||
let sp = GenericService::new(NAME, true);
|
||||
sp.run(run);
|
||||
@@ -176,6 +140,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,
|
||||
@@ -201,9 +173,11 @@ fn check_display_changed(
|
||||
}
|
||||
|
||||
// Capturer object is expensive, avoiding to create it frequently.
|
||||
fn create_capturer(privacy_mode_id: i32, display: Display) -> ResultType<Box<dyn TraitCapturer>> {
|
||||
let use_yuv = true;
|
||||
|
||||
fn create_capturer(
|
||||
privacy_mode_id: i32,
|
||||
display: Display,
|
||||
use_yuv: bool,
|
||||
) -> ResultType<Box<dyn TraitCapturer>> {
|
||||
#[cfg(not(windows))]
|
||||
let c: Option<Box<dyn TraitCapturer>> = None;
|
||||
#[cfg(windows)]
|
||||
@@ -288,11 +262,12 @@ 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 _ {
|
||||
if let Ok((_, _, display)) = get_current_display() {
|
||||
if let Ok(_) = create_capturer(privacy_mode_id, display) {
|
||||
if let Ok(_) = create_capturer(privacy_mode_id, display, true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -316,13 +291,39 @@ 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 fps = 30;
|
||||
let wait = 1000 / fps;
|
||||
let spf = time::Duration::from_secs_f32(1. / (fps as f32));
|
||||
let (ndisplay, current, display) = get_current_display()?;
|
||||
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
||||
log::debug!(
|
||||
@@ -342,8 +343,10 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
#[cfg(windows)]
|
||||
let mut captuerer_privacy_mode_id = privacy_mode_id;
|
||||
#[cfg(windows)]
|
||||
if crate::ui::win_privacy::is_process_consent_running()? {
|
||||
captuerer_privacy_mode_id = 0;
|
||||
if captuerer_privacy_mode_id != 0 {
|
||||
if crate::ui::win_privacy::is_process_consent_running()? {
|
||||
captuerer_privacy_mode_id = 0;
|
||||
}
|
||||
}
|
||||
log::debug!(
|
||||
"Try create capturer with captuerer privacy mode id {}",
|
||||
@@ -355,36 +358,66 @@ 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)?;
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
let q = get_image_quality();
|
||||
let (bitrate, rc_min_quantizer, rc_max_quantizer, speed) = get_quality(width, height, q);
|
||||
log::info!("bitrate={}, rc_min_quantizer={}", bitrate, rc_min_quantizer);
|
||||
let cfg = Config {
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
timebase: [1, 1000], // Output timestamp precision
|
||||
bitrate,
|
||||
codec: VideoCodecId::VP9,
|
||||
rc_min_quantizer,
|
||||
rc_max_quantizer,
|
||||
speed,
|
||||
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 vpx;
|
||||
match Encoder::new(&cfg, (num_cpus::get() / 2) as _) {
|
||||
Ok(x) => vpx = x,
|
||||
|
||||
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();
|
||||
@@ -401,21 +434,35 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
let mut try_gdi = 1;
|
||||
#[cfg(windows)]
|
||||
log::info!("gdi: {}", c.is_gdi());
|
||||
let codec_name = Encoder::current_hw_encoder_name();
|
||||
|
||||
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();
|
||||
if video_qos.check_if_updated() {
|
||||
log::debug!(
|
||||
"qos is updated, target_bitrate:{}, fps:{}",
|
||||
video_qos.target_bitrate,
|
||||
video_qos.fps
|
||||
);
|
||||
encoder.set_bitrate(video_qos.target_bitrate).unwrap();
|
||||
spf = video_qos.spf();
|
||||
}
|
||||
drop(video_qos);
|
||||
|
||||
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)?;
|
||||
if get_image_quality() != q {
|
||||
if codec_name != Encoder::current_hw_encoder_name() {
|
||||
bail!("SWITCH");
|
||||
}
|
||||
check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if crate::platform::windows::desktop_changed() {
|
||||
@@ -425,7 +472,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");
|
||||
@@ -437,7 +484,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
frame_controller.reset();
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let res = match (*c).frame(wait as _) {
|
||||
let res = match c.frame(spf) {
|
||||
Ok(frame) => {
|
||||
let time = now - start;
|
||||
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
||||
@@ -448,7 +495,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
}
|
||||
scrap::Frame::RAW(data) => {
|
||||
if (data.len() != 0) {
|
||||
let send_conn_ids = handle_one_frame(&sp, data, ms, &mut vpx)?;
|
||||
let send_conn_ids = handle_one_frame(&sp, data, ms, &mut encoder)?;
|
||||
frame_controller.set_send(now, send_conn_ids);
|
||||
}
|
||||
}
|
||||
@@ -460,11 +507,11 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let res = match (*c).frame(wait as _) {
|
||||
let res = match c.frame(spf) {
|
||||
Ok(frame) => {
|
||||
let time = now - start;
|
||||
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
||||
let send_conn_ids = handle_one_frame(&sp, &frame, ms, &mut vpx)?;
|
||||
let send_conn_ids = handle_one_frame(&sp, &frame, ms, &mut encoder)?;
|
||||
frame_controller.set_send(now, send_conn_ids);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -489,7 +536,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");
|
||||
@@ -511,9 +558,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() {
|
||||
@@ -531,13 +578,12 @@ fn run(sp: GenericService) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> ResultType<()> {
|
||||
let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
||||
if privacy_mode_id != privacy_mode_id_2 {
|
||||
if privacy_mode_id_2 != 0 {
|
||||
let msg_out = crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::OnByOther,
|
||||
back_notification::PrivacyModeState::PrvOnByOther,
|
||||
);
|
||||
sp.send_to_others(msg_out, privacy_mode_id_2);
|
||||
}
|
||||
@@ -547,10 +593,11 @@ fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> Resu
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn create_msg(vp9s: Vec<VP9>) -> Message {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
fn create_msg(vp9s: Vec<EncodedVideoFrame>) -> Message {
|
||||
let mut msg_out = Message::new();
|
||||
let mut vf = VideoFrame::new();
|
||||
vf.set_vp9s(VP9s {
|
||||
vf.set_vp9s(EncodedVideoFrames {
|
||||
frames: vp9s.into(),
|
||||
..Default::default()
|
||||
});
|
||||
@@ -559,22 +606,12 @@ fn create_msg(vp9s: Vec<VP9>) -> Message {
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn create_frame(frame: &EncodeFrame) -> VP9 {
|
||||
VP9 {
|
||||
data: frame.data.to_vec(),
|
||||
key: frame.key,
|
||||
pts: frame.pts,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handle_one_frame(
|
||||
sp: &GenericService,
|
||||
frame: &[u8],
|
||||
ms: i64,
|
||||
vpx: &mut Encoder,
|
||||
encoder: &mut Encoder,
|
||||
) -> ResultType<HashSet<i32>> {
|
||||
sp.snapshot(|sps| {
|
||||
// so that new sub and old sub share the same encoder after switch
|
||||
@@ -585,20 +622,8 @@ fn handle_one_frame(
|
||||
})?;
|
||||
|
||||
let mut send_conn_ids: HashSet<i32> = Default::default();
|
||||
let mut frames = Vec::new();
|
||||
for ref frame in vpx
|
||||
.encode(ms, frame, STRIDE_ALIGN)
|
||||
.with_context(|| "Failed to encode")?
|
||||
{
|
||||
frames.push(create_frame(frame));
|
||||
}
|
||||
for ref frame in vpx.flush().with_context(|| "Failed to flush")? {
|
||||
frames.push(create_frame(frame));
|
||||
}
|
||||
|
||||
// to-do: flush periodically, e.g. 1 second
|
||||
if frames.len() > 0 {
|
||||
send_conn_ids = sp.send_video_frame(create_msg(frames));
|
||||
if let Ok(msg) = encoder.encode_to_message(frame, ms) {
|
||||
send_conn_ids = sp.send_video_frame(msg);
|
||||
}
|
||||
Ok(send_conn_ids)
|
||||
}
|
||||
@@ -618,8 +643,8 @@ pub fn handle_one_frame_encoded(
|
||||
Ok(())
|
||||
})?;
|
||||
let mut send_conn_ids: HashSet<i32> = Default::default();
|
||||
let vp9_frame = VP9 {
|
||||
data: frame.to_vec(),
|
||||
let vp9_frame = EncodedVideoFrame {
|
||||
data: frame.to_vec().into(),
|
||||
key: true,
|
||||
pts: ms,
|
||||
..Default::default()
|
||||
@@ -629,6 +654,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 {
|
||||
@@ -636,14 +672,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;
|
||||
}
|
||||
@@ -661,12 +693,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;
|
||||
}
|
||||
@@ -680,6 +726,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() {
|
||||
@@ -690,8 +746,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))]
|
||||
@@ -729,16 +785,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;
|
||||
@@ -746,84 +801,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)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_latency(id: i32, latency: i64, latencies: &mut HashMap<i32, i64>) {
|
||||
if latency <= 0 {
|
||||
latencies.remove(&id);
|
||||
} else {
|
||||
latencies.insert(id, latency);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_test_latency(id: i32, latency: i64) {
|
||||
update_latency(id, latency, &mut *TEST_LATENCIES.lock().unwrap());
|
||||
}
|
||||
|
||||
fn convert_quality(q: i32) -> i32 {
|
||||
let q = {
|
||||
if q == ImageQuality::Balanced.value() {
|
||||
(100 * 2 / 3, 12)
|
||||
} else if q == ImageQuality::Low.value() {
|
||||
(100 / 2, 18)
|
||||
} else if q == ImageQuality::Best.value() {
|
||||
(100, 12)
|
||||
} else {
|
||||
let bitrate = q >> 8 & 0xFF;
|
||||
let quantizer = q & 0xFF;
|
||||
(bitrate * 2, (100 - quantizer) * 36 / 100)
|
||||
}
|
||||
};
|
||||
if q.0 <= 0 {
|
||||
0
|
||||
} else {
|
||||
q.0 << 8 | q.1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_image_quality(id: i32, q: Option<i32>) {
|
||||
match q {
|
||||
Some(q) => {
|
||||
let q = convert_quality(q);
|
||||
if q > 0 {
|
||||
IMAGE_QUALITIES.lock().unwrap().insert(id, q);
|
||||
} else {
|
||||
IMAGE_QUALITIES.lock().unwrap().remove(&id);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
IMAGE_QUALITIES.lock().unwrap().remove(&id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_image_quality() -> i32 {
|
||||
IMAGE_QUALITIES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.values()
|
||||
.min()
|
||||
.unwrap_or(&convert_quality(ImageQuality::Balanced.value()))
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_quality(w: usize, h: usize, q: i32) -> (u32, u32, u32, i32) {
|
||||
// https://www.nvidia.com/en-us/geforce/guides/broadcasting-guide/
|
||||
let bitrate = q >> 8 & 0xFF;
|
||||
let quantizer = q & 0xFF;
|
||||
let b = ((w * h) / 1000) as u32;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
// fix when andorid screen shrinks
|
||||
let fix = Display::fix_quality() as u32;
|
||||
log::debug!("Android screen, fix quality:{}", fix);
|
||||
let b = b * fix;
|
||||
return (bitrate as u32 * b / 100, quantizer as _, 56, 7);
|
||||
}
|
||||
|
||||
(bitrate as u32 * b / 100, quantizer as _, 56, 7)
|
||||
fn get_current_display() -> ResultType<(usize, usize, Display)> {
|
||||
get_current_display_2(try_get_displays()?)
|
||||
}
|
||||
|
||||
189
src/server/wayland.rs
Normal file
189
src/server/wayland.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
use super::*;
|
||||
use hbb_common::allow_err;
|
||||
use scrap::{Capturer, Display, Frame, TraitCapturer};
|
||||
use std::io::Result;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CAP_DISPLAY_INFO: RwLock<u64> = RwLock::new(0);
|
||||
}
|
||||
|
||||
struct CapturerPtr(*mut Capturer);
|
||||
|
||||
impl Clone for CapturerPtr {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl TraitCapturer for CapturerPtr {
|
||||
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>> {
|
||||
unsafe { (*self.0).frame(timeout) }
|
||||
}
|
||||
|
||||
fn set_use_yuv(&mut self, use_yuv: bool) {
|
||||
unsafe {
|
||||
(*self.0).set_use_yuv(use_yuv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CapDisplayInfo {
|
||||
rects: Vec<((i32, i32), usize, usize)>,
|
||||
displays: Vec<DisplayInfo>,
|
||||
num: usize,
|
||||
primary: usize,
|
||||
current: usize,
|
||||
capturer: CapturerPtr,
|
||||
}
|
||||
|
||||
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 capturer = CapturerPtr(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.0);
|
||||
}
|
||||
*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.clone()),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
bail!("Failed to get capturer display info");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user