mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-03-12 15:51:10 +03:00
feat: remote printer (#11231)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
@@ -28,7 +28,7 @@ use hbb_common::platform::linux::run_cmds;
|
||||
use hbb_common::protobuf::EnumOrUnknown;
|
||||
use hbb_common::{
|
||||
config::{self, keys, Config, TrustedDevice},
|
||||
fs::{self, can_enable_overwrite_detection},
|
||||
fs::{self, can_enable_overwrite_detection, JobType},
|
||||
futures::{SinkExt, StreamExt},
|
||||
get_time, get_version_number,
|
||||
message_proto::{option_message::BoolOption, permission_info::Permission},
|
||||
@@ -67,7 +67,7 @@ lazy_static::lazy_static! {
|
||||
static ref LOGIN_FAILURES: [Arc::<Mutex<HashMap<String, (i32, i32, i32)>>>; 2] = Default::default();
|
||||
static ref SESSIONS: Arc::<Mutex<HashMap<SessionKey, Session>>> = Default::default();
|
||||
static ref ALIVE_CONNS: Arc::<Mutex<Vec<i32>>> = Default::default();
|
||||
pub static ref AUTHED_CONNS: Arc::<Mutex<Vec<(i32, AuthConnType, SessionKey)>>> = Default::default();
|
||||
pub static ref AUTHED_CONNS: Arc::<Mutex<Vec<AuthedConn>>> = Default::default();
|
||||
static ref SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid)>>> = Default::default();
|
||||
static ref WAKELOCK_SENDER: Arc::<Mutex<std::sync::mpsc::Sender<(usize, usize)>>> = Arc::new(Mutex::new(start_wakelock_thread()));
|
||||
}
|
||||
@@ -245,6 +245,8 @@ pub struct Connection {
|
||||
follow_remote_cursor: bool,
|
||||
follow_remote_window: bool,
|
||||
multi_ui_session: bool,
|
||||
tx_from_authed: mpsc::UnboundedSender<ipc::Data>,
|
||||
printer_data: Vec<(Instant, String, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl ConnInner {
|
||||
@@ -309,6 +311,7 @@ impl Connection {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<(Instant, Arc<Message>)>();
|
||||
let (tx_video, mut rx_video) = mpsc::unbounded_channel::<(Instant, Arc<Message>)>();
|
||||
let (tx_input, _rx_input) = std_mpsc::channel();
|
||||
let (tx_from_authed, mut rx_from_authed) = mpsc::unbounded_channel::<ipc::Data>();
|
||||
let mut hbbs_rx = crate::hbbs_http::sync::signal_receiver();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let (tx_cm_stream_ready, _rx_cm_stream_ready) = mpsc::channel(1);
|
||||
@@ -396,6 +399,8 @@ impl Connection {
|
||||
delayed_read_dir: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
retina: Retina::default(),
|
||||
tx_from_authed,
|
||||
printer_data: Vec::new(),
|
||||
};
|
||||
let addr = hbb_common::try_into_v4(addr);
|
||||
if !conn.on_open(addr).await {
|
||||
@@ -758,6 +763,19 @@ impl Connection {
|
||||
break;
|
||||
}
|
||||
},
|
||||
Some(data) = rx_from_authed.recv() => {
|
||||
match data {
|
||||
#[cfg(all(target_os = "windows", feature = "flutter"))]
|
||||
ipc::Data::PrinterData(data) => {
|
||||
if config::Config::get_bool_option(config::keys::OPTION_ENABLE_REMOTE_PRINTER) {
|
||||
conn.send_printer_request(data).await;
|
||||
} else {
|
||||
conn.send_remote_printing_disallowed().await;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ = second_timer.tick() => {
|
||||
#[cfg(windows)]
|
||||
conn.portable_check();
|
||||
@@ -1104,6 +1122,22 @@ impl Connection {
|
||||
});
|
||||
}
|
||||
|
||||
fn get_files_for_audit(job_type: fs::JobType, mut files: Vec<FileEntry>) -> Vec<(String, i64)> {
|
||||
files
|
||||
.drain(..)
|
||||
.map(|f| {
|
||||
(
|
||||
if job_type == fs::JobType::Printer {
|
||||
"Remote print".to_owned()
|
||||
} else {
|
||||
f.name
|
||||
},
|
||||
f.size as _,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn post_file_audit(
|
||||
&self,
|
||||
r#type: FileAuditType,
|
||||
@@ -1212,6 +1246,8 @@ impl Connection {
|
||||
self.inner.id(),
|
||||
auth_conn_type,
|
||||
self.session_key(),
|
||||
self.tx_from_authed.clone(),
|
||||
self.lr.clone(),
|
||||
));
|
||||
self.session_last_recv_time = SESSIONS
|
||||
.lock()
|
||||
@@ -2318,7 +2354,15 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
Some(message::Union::FileAction(fa)) => {
|
||||
if self.file_transfer.is_some() {
|
||||
let mut handle_fa = self.file_transfer.is_some();
|
||||
if !handle_fa {
|
||||
if let Some(file_action::Union::Send(s)) = fa.union.as_ref() {
|
||||
if JobType::from_proto(s.file_type) == JobType::Printer {
|
||||
handle_fa = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if handle_fa {
|
||||
if self.delayed_read_dir.is_some() {
|
||||
if let Some(file_action::Union::ReadDir(rd)) = fa.union {
|
||||
self.delayed_read_dir = Some((rd.path, rd.include_hidden));
|
||||
@@ -2375,10 +2419,32 @@ impl Connection {
|
||||
&self.lr.version,
|
||||
));
|
||||
let path = s.path.clone();
|
||||
let r#type = JobType::from_proto(s.file_type);
|
||||
let data_source;
|
||||
match r#type {
|
||||
JobType::Generic => {
|
||||
data_source =
|
||||
fs::DataSource::FilePath(PathBuf::from(&path));
|
||||
}
|
||||
JobType::Printer => {
|
||||
if let Some(pd) =
|
||||
self.printer_data.iter().find(|(_, p, _)| *p == path)
|
||||
{
|
||||
data_source = fs::DataSource::MemoryCursor(
|
||||
std::io::Cursor::new(pd.2.clone()),
|
||||
);
|
||||
self.printer_data.retain(|f| f.1 != path);
|
||||
} else {
|
||||
// Ignore this message if the printer data is not found
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
match fs::TransferJob::new_read(
|
||||
id,
|
||||
r#type,
|
||||
"".to_string(),
|
||||
path.clone(),
|
||||
data_source,
|
||||
s.file_num,
|
||||
s.include_hidden,
|
||||
false,
|
||||
@@ -2390,19 +2456,21 @@ impl Connection {
|
||||
Ok(mut job) => {
|
||||
self.send(fs::new_dir(id, path, job.files().to_vec()))
|
||||
.await;
|
||||
let mut files = job.files().to_owned();
|
||||
let files = job.files().to_owned();
|
||||
job.is_remote = true;
|
||||
job.conn_id = self.inner.id();
|
||||
let job_type = job.r#type;
|
||||
self.read_jobs.push(job);
|
||||
self.file_timer =
|
||||
crate::rustdesk_interval(time::interval(MILLI1));
|
||||
self.post_file_audit(
|
||||
FileAuditType::RemoteSend,
|
||||
&s.path,
|
||||
files
|
||||
.drain(..)
|
||||
.map(|f| (f.name, f.size as _))
|
||||
.collect(),
|
||||
if job_type == fs::JobType::Printer {
|
||||
"Remote print"
|
||||
} else {
|
||||
&s.path
|
||||
},
|
||||
Self::get_files_for_audit(job_type, files),
|
||||
json!({}),
|
||||
);
|
||||
}
|
||||
@@ -2433,11 +2501,7 @@ impl Connection {
|
||||
self.post_file_audit(
|
||||
FileAuditType::RemoteReceive,
|
||||
&r.path,
|
||||
r.files
|
||||
.to_vec()
|
||||
.drain(..)
|
||||
.map(|f| (f.name, f.size as _))
|
||||
.collect(),
|
||||
Self::get_files_for_audit(fs::JobType::Generic, r.files),
|
||||
json!({}),
|
||||
);
|
||||
self.file_transferred = true;
|
||||
@@ -2476,13 +2540,12 @@ impl Connection {
|
||||
}
|
||||
Some(file_action::Union::Cancel(c)) => {
|
||||
self.send_fs(ipc::FS::CancelWrite { id: c.id });
|
||||
if let Some(job) = fs::get_job_immutable(c.id, &self.read_jobs) {
|
||||
if let Some(job) = fs::remove_job(c.id, &mut self.read_jobs) {
|
||||
self.send_to_cm(ipc::Data::FileTransferLog((
|
||||
"transfer".to_string(),
|
||||
fs::serialize_transfer_job(job, false, true, ""),
|
||||
fs::serialize_transfer_job(&job, false, true, ""),
|
||||
)));
|
||||
}
|
||||
fs::remove_job(c.id, &mut self.read_jobs);
|
||||
}
|
||||
Some(file_action::Union::SendConfirm(r)) => {
|
||||
if let Some(job) = fs::get_job(r.id, &mut self.read_jobs) {
|
||||
@@ -3635,6 +3698,32 @@ impl Connection {
|
||||
fn try_empty_file_clipboard(&mut self) {
|
||||
try_empty_clipboard_files(ClipboardSide::Host, self.inner.id());
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "windows", feature = "flutter"))]
|
||||
async fn send_printer_request(&mut self, data: Vec<u8>) {
|
||||
// This path is only used to identify the printer job.
|
||||
let path = format!("RustDesk://FsJob//Printer/{}", get_time());
|
||||
|
||||
let msg = fs::new_send(0, fs::JobType::Printer, path.clone(), 1, false);
|
||||
self.send(msg).await;
|
||||
self.printer_data
|
||||
.retain(|(t, _, _)| t.elapsed().as_secs() < 60);
|
||||
self.printer_data.push((Instant::now(), path, data));
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "windows", feature = "flutter"))]
|
||||
async fn send_remote_printing_disallowed(&mut self) {
|
||||
let mut msg_out = Message::new();
|
||||
let res = MessageBox {
|
||||
msgtype: "custom-nook-nocancel-hasclose".to_owned(),
|
||||
title: "remote-printing-disallowed-tile-tip".to_owned(),
|
||||
text: "remote-printing-disallowed-text-tip".to_owned(),
|
||||
link: "".to_owned(),
|
||||
..Default::default()
|
||||
};
|
||||
msg_out.set_message_box(res);
|
||||
self.send(msg_out).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
|
||||
@@ -3970,6 +4059,19 @@ fn start_wakelock_thread() -> std::sync::mpsc::Sender<(usize, usize)> {
|
||||
tx
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "windows", feature = "flutter"))]
|
||||
pub fn on_printer_data(data: Vec<u8>) {
|
||||
crate::server::AUTHED_CONNS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|c| c.printer)
|
||||
.next()
|
||||
.map(|c| {
|
||||
c.sender.send(Data::PrinterData(data)).ok();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub struct PortableState {
|
||||
pub last_uac: bool,
|
||||
@@ -4103,6 +4205,14 @@ impl Retina {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuthedConn {
|
||||
pub conn_id: i32,
|
||||
pub conn_type: AuthConnType,
|
||||
pub session_key: SessionKey,
|
||||
pub sender: mpsc::UnboundedSender<Data>,
|
||||
pub printer: bool,
|
||||
}
|
||||
|
||||
mod raii {
|
||||
// ALIVE_CONNS: all connections, including unauthorized connections
|
||||
// AUTHED_CONNS: all authorized connections
|
||||
@@ -4127,11 +4237,23 @@ mod raii {
|
||||
pub struct AuthedConnID(i32, AuthConnType);
|
||||
|
||||
impl AuthedConnID {
|
||||
pub fn new(conn_id: i32, conn_type: AuthConnType, session_key: SessionKey) -> Self {
|
||||
AUTHED_CONNS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((conn_id, conn_type, session_key));
|
||||
pub fn new(
|
||||
conn_id: i32,
|
||||
conn_type: AuthConnType,
|
||||
session_key: SessionKey,
|
||||
sender: mpsc::UnboundedSender<Data>,
|
||||
lr: LoginRequest,
|
||||
) -> Self {
|
||||
let printer = conn_type == crate::server::AuthConnType::Remote
|
||||
&& crate::is_support_remote_print(&lr.version)
|
||||
&& lr.my_platform == whoami::Platform::Windows.to_string();
|
||||
AUTHED_CONNS.lock().unwrap().push(AuthedConn {
|
||||
conn_id,
|
||||
conn_type,
|
||||
session_key,
|
||||
sender,
|
||||
printer,
|
||||
});
|
||||
Self::check_wake_lock();
|
||||
use std::sync::Once;
|
||||
static _ONCE: Once = Once::new();
|
||||
@@ -4153,7 +4275,7 @@ mod raii {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|c| c.1 == AuthConnType::Remote)
|
||||
.filter(|c| c.conn_type == AuthConnType::Remote)
|
||||
.count();
|
||||
allow_err!(WAKELOCK_SENDER
|
||||
.lock()
|
||||
@@ -4166,7 +4288,7 @@ mod raii {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|c| c.1 != AuthConnType::PortForward)
|
||||
.filter(|c| c.conn_type != AuthConnType::PortForward)
|
||||
.count()
|
||||
}
|
||||
|
||||
@@ -4179,16 +4301,16 @@ mod raii {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|c| c.0 == conn_id && c.1 == AuthConnType::Remote);
|
||||
.any(|c| c.conn_id == conn_id && c.conn_type == AuthConnType::Remote);
|
||||
// If there are 2 connections with the same peer_id and session_id, a remote connection and a file transfer or port forward connection,
|
||||
// If any of the connections is closed allowing retry, this will not be called;
|
||||
// If the file transfer/port forward connection is closed with no retry, the session should be kept for remote control menu action;
|
||||
// If the remote connection is closed with no retry, keep the session is not reasonable in case there is a retry button in the remote side, and ignore network fluctuations.
|
||||
let another_remote = AUTHED_CONNS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|c| c.0 != conn_id && c.2 == key && c.1 == AuthConnType::Remote);
|
||||
let another_remote = AUTHED_CONNS.lock().unwrap().iter().any(|c| {
|
||||
c.conn_id != conn_id
|
||||
&& c.session_key == key
|
||||
&& c.conn_type == AuthConnType::Remote
|
||||
});
|
||||
if is_remote || !another_remote {
|
||||
lock.remove(&key);
|
||||
log::info!("remove session");
|
||||
@@ -4256,12 +4378,12 @@ mod raii {
|
||||
.unwrap()
|
||||
.on_connection_close(self.0);
|
||||
}
|
||||
AUTHED_CONNS.lock().unwrap().retain(|c| c.0 != self.0);
|
||||
AUTHED_CONNS.lock().unwrap().retain(|c| c.conn_id != self.0);
|
||||
let remote_count = AUTHED_CONNS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|c| c.1 == AuthConnType::Remote)
|
||||
.filter(|c| c.conn_type == AuthConnType::Remote)
|
||||
.count();
|
||||
if remote_count == 0 {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
|
||||
@@ -505,7 +505,7 @@ pub fn try_stop_record_cursor_pos() {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|c| c.1 == AuthConnType::Remote)
|
||||
.filter(|c| c.conn_type == AuthConnType::Remote)
|
||||
.count();
|
||||
if remote_count > 0 {
|
||||
return;
|
||||
|
||||
@@ -812,7 +812,7 @@ pub mod client {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|c| c.1 == crate::server::AuthConnType::Remote)
|
||||
.filter(|c| c.conn_type == crate::server::AuthConnType::Remote)
|
||||
.count();
|
||||
stream.send(&Data::DataPortableService(ConnCount(Some(remote_count)))).await.ok();
|
||||
}
|
||||
|
||||
163
src/server/printer_service.rs
Normal file
163
src/server/printer_service.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use super::service::{EmptyExtraFieldService, GenericService, Service};
|
||||
use hbb_common::{bail, dlopen::symbor::Library, log, ResultType};
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub const NAME: &'static str = "remote-printer";
|
||||
|
||||
const LIB_NAME_PRINTER_DRIVER_ADAPTER: &str = "printer_driver_adapter";
|
||||
|
||||
// Return 0 if success, otherwise return error code.
|
||||
pub type Init = fn(tag_name: *const i8) -> i32;
|
||||
pub type Uninit = fn();
|
||||
// dur_mills: Get the file generated in the last `dur_mills` milliseconds.
|
||||
// data: The raw prn data, xps format.
|
||||
// data_len: The length of the raw prn data.
|
||||
pub type GetPrnData = fn(dur_mills: u32, data: *mut *mut i8, data_len: *mut u32);
|
||||
|
||||
macro_rules! make_lib_wrapper {
|
||||
($($field:ident : $tp:ty),+) => {
|
||||
struct LibWrapper {
|
||||
_lib: Option<Library>,
|
||||
$($field: Option<$tp>),+
|
||||
}
|
||||
|
||||
impl LibWrapper {
|
||||
fn new() -> Self {
|
||||
let lib_name = match get_lib_name() {
|
||||
Ok(name) => name,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to get lib name, {}", e);
|
||||
return Self {
|
||||
_lib: None,
|
||||
$( $field: None ),+
|
||||
};
|
||||
}
|
||||
};
|
||||
let lib = match Library::open(&lib_name) {
|
||||
Ok(lib) => Some(lib),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to load library {}, {}", &lib_name, e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
$(let $field = if let Some(lib) = &lib {
|
||||
match unsafe { lib.symbol::<$tp>(stringify!($field)) } {
|
||||
Ok(m) => {
|
||||
log::info!("method found {}", stringify!($field));
|
||||
Some(*m)
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to load func {}, {}", stringify!($field), e);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};)+
|
||||
|
||||
Self {
|
||||
_lib: lib,
|
||||
$( $field ),+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LibWrapper {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
make_lib_wrapper!(
|
||||
init: Init,
|
||||
uninit: Uninit,
|
||||
get_prn_data: GetPrnData
|
||||
);
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref LIB_WRAPPER: Arc<Mutex<LibWrapper>> = Default::default();
|
||||
}
|
||||
|
||||
fn get_lib_name() -> ResultType<String> {
|
||||
let exe_file = std::env::current_exe()?;
|
||||
if let Some(cur_dir) = exe_file.parent() {
|
||||
let dll_name = format!("{}.dll", LIB_NAME_PRINTER_DRIVER_ADAPTER);
|
||||
let full_path = cur_dir.join(dll_name);
|
||||
if !full_path.exists() {
|
||||
bail!("{} not found", full_path.to_string_lossy().as_ref());
|
||||
} else {
|
||||
Ok(full_path.to_string_lossy().into_owned())
|
||||
}
|
||||
} else {
|
||||
bail!(
|
||||
"Invalid exe parent for {}",
|
||||
exe_file.to_string_lossy().as_ref()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(app_name: &str) -> ResultType<()> {
|
||||
let lib_wrapper = LIB_WRAPPER.lock().unwrap();
|
||||
let Some(fn_init) = lib_wrapper.init.as_ref() else {
|
||||
bail!("Failed to load func init");
|
||||
};
|
||||
|
||||
let tag_name = std::ffi::CString::new(app_name)?;
|
||||
let ret = fn_init(tag_name.as_ptr());
|
||||
if ret != 0 {
|
||||
bail!("Failed to init printer driver");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uninit() {
|
||||
let lib_wrapper = LIB_WRAPPER.lock().unwrap();
|
||||
if let Some(fn_uninit) = lib_wrapper.uninit.as_ref() {
|
||||
fn_uninit();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_prn_data(dur_mills: u32) -> ResultType<Vec<u8>> {
|
||||
let lib_wrapper = LIB_WRAPPER.lock().unwrap();
|
||||
if let Some(fn_get_prn_data) = lib_wrapper.get_prn_data.as_ref() {
|
||||
let mut data = std::ptr::null_mut();
|
||||
let mut data_len = 0u32;
|
||||
fn_get_prn_data(dur_mills, &mut data, &mut data_len);
|
||||
if data.is_null() || data_len == 0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let bytes =
|
||||
Vec::from(unsafe { std::slice::from_raw_parts(data as *const u8, data_len as usize) });
|
||||
unsafe {
|
||||
hbb_common::libc::free(data as *mut std::ffi::c_void);
|
||||
}
|
||||
Ok(bytes)
|
||||
} else {
|
||||
bail!("Failed to load func get_prn_file");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(name: String) -> GenericService {
|
||||
let svc = EmptyExtraFieldService::new(name, false);
|
||||
GenericService::run(&svc.clone(), run);
|
||||
svc.sp
|
||||
}
|
||||
|
||||
fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
||||
while sp.ok() {
|
||||
let bytes = get_prn_data(1000)?;
|
||||
if !bytes.is_empty() {
|
||||
log::info!("Got prn data, data len: {}", bytes.len());
|
||||
crate::server::on_printer_data(bytes);
|
||||
}
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user