feat: remote printer (#11231)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2025-03-27 15:34:27 +08:00
committed by GitHub
parent 1cb53c1f7a
commit f4bbf82363
101 changed files with 3707 additions and 211 deletions

View File

@@ -49,6 +49,7 @@ use hbb_common::{
self, Config, LocalConfig, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT,
READ_TIMEOUT, RELAY_PORT, RENDEZVOUS_PORT, RENDEZVOUS_SERVERS,
},
fs::JobType,
get_version_number, log,
message_proto::{option_message::BoolOption, *},
protobuf::{Message as _, MessageField},
@@ -3297,7 +3298,7 @@ pub enum Data {
Close,
Login((String, String, String, bool)),
Message(Message),
SendFiles((i32, String, String, i32, bool, bool)),
SendFiles((i32, JobType, String, String, i32, bool, bool)),
RemoveDirAll((i32, String, bool, bool)),
ConfirmDeleteFiles((i32, i32)),
SetNoConfirm(i32),
@@ -3311,7 +3312,7 @@ pub enum Data {
ToggleClipboardFile,
NewRDP,
SetConfirmOverrideFile((i32, i32, bool, bool, bool)),
AddJob((i32, String, String, i32, bool, bool)),
AddJob((i32, JobType, String, String, i32, bool, bool)),
ResumeJob((i32, bool)),
RecordScreen(bool),
ElevateDirect,

View File

@@ -7,6 +7,14 @@ pub trait FileManager: Interface {
fs::get_home_as_string()
}
fn get_next_job_id(&self) -> i32 {
fs::get_next_job_id()
}
fn update_next_job_id(&self, id: i32) {
fs::update_next_job_id(id);
}
#[cfg(not(any(
target_os = "android",
target_os = "ios",
@@ -98,6 +106,7 @@ pub trait FileManager: Interface {
fn send_files(
&self,
id: i32,
r#type: i32,
path: String,
to: String,
file_num: i32,
@@ -106,6 +115,7 @@ pub trait FileManager: Interface {
) {
self.send(Data::SendFiles((
id,
r#type.into(),
path,
to,
file_num,
@@ -117,6 +127,7 @@ pub trait FileManager: Interface {
fn add_job(
&self,
id: i32,
r#type: i32,
path: String,
to: String,
file_num: i32,
@@ -125,6 +136,7 @@ pub trait FileManager: Interface {
) {
self.send(Data::AddJob((
id,
r#type.into(),
path,
to,
file_num,

View File

@@ -46,6 +46,7 @@ use std::{
collections::HashMap,
ffi::c_void,
num::NonZeroI64,
path::PathBuf,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, RwLock,
@@ -549,13 +550,20 @@ impl<T: InvokeUiSession> Remote<T> {
}
allow_err!(peer.send(&msg).await);
}
Data::SendFiles((id, path, to, file_num, include_hidden, is_remote)) => {
Data::SendFiles((id, r#type, path, to, file_num, include_hidden, is_remote)) => {
log::info!("send files, is remote {}", is_remote);
let od = can_enable_overwrite_detection(self.handler.lc.read().unwrap().version);
if is_remote {
log::debug!("New job {}, write to {} from remote {}", id, to, path);
let to = match r#type {
fs::JobType::Generic => fs::DataSource::FilePath(PathBuf::from(&to)),
fs::JobType::Printer => {
fs::DataSource::MemoryCursor(std::io::Cursor::new(Vec::new()))
}
};
self.write_jobs.push(fs::TransferJob::new_write(
id,
r#type,
path.clone(),
to,
file_num,
@@ -565,14 +573,15 @@ impl<T: InvokeUiSession> Remote<T> {
od,
));
allow_err!(
peer.send(&fs::new_send(id, path, file_num, include_hidden))
peer.send(&fs::new_send(id, r#type, path, file_num, include_hidden))
.await
);
} else {
match fs::TransferJob::new_read(
id,
r#type,
to.clone(),
path.clone(),
fs::DataSource::FilePath(PathBuf::from(&path)),
file_num,
include_hidden,
is_remote,
@@ -616,7 +625,7 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
}
Data::AddJob((id, path, to, file_num, include_hidden, is_remote)) => {
Data::AddJob((id, r#type, path, to, file_num, include_hidden, is_remote)) => {
let od = can_enable_overwrite_detection(self.handler.lc.read().unwrap().version);
if is_remote {
log::debug!(
@@ -627,8 +636,9 @@ impl<T: InvokeUiSession> Remote<T> {
);
let mut job = fs::TransferJob::new_write(
id,
r#type,
path.clone(),
to,
fs::DataSource::FilePath(PathBuf::from(&to)),
file_num,
include_hidden,
is_remote,
@@ -640,8 +650,9 @@ impl<T: InvokeUiSession> Remote<T> {
} else {
match fs::TransferJob::new_read(
id,
r#type,
to.clone(),
path.clone(),
fs::DataSource::FilePath(PathBuf::from(&path)),
file_num,
include_hidden,
is_remote,
@@ -679,6 +690,7 @@ impl<T: InvokeUiSession> Remote<T> {
allow_err!(
peer.send(&fs::new_send(
id,
fs::JobType::Generic,
job.remote.clone(),
job.file_num,
job.show_hidden
@@ -688,17 +700,25 @@ impl<T: InvokeUiSession> Remote<T> {
}
} else {
if let Some(job) = get_job(id, &mut self.read_jobs) {
job.is_last_job = false;
allow_err!(
peer.send(&fs::new_receive(
id,
job.path.to_string_lossy().to_string(),
job.file_num,
job.files.clone(),
job.total_size(),
))
.await
);
match &job.data_source {
fs::DataSource::FilePath(p) => {
job.is_last_job = false;
allow_err!(
peer.send(&fs::new_receive(
id,
p.to_string_lossy().to_string(),
job.file_num,
job.files.clone(),
job.total_size(),
))
.await
);
}
fs::DataSource::MemoryCursor(_) => {
// unreachable!()
log::error!("Resume job with memory cursor");
}
}
}
}
}
@@ -803,11 +823,10 @@ impl<T: InvokeUiSession> Remote<T> {
});
msg_out.set_file_action(file_action);
allow_err!(peer.send(&msg_out).await);
if let Some(job) = fs::get_job(id, &mut self.write_jobs) {
if let Some(job) = fs::remove_job(id, &mut self.write_jobs) {
job.remove_download_file();
fs::remove_job(id, &mut self.write_jobs);
}
fs::remove_job(id, &mut self.read_jobs);
let _ = fs::remove_job(id, &mut self.read_jobs);
self.remove_jobs.remove(&id);
}
Data::RemoveDir((id, path)) => {
@@ -1402,92 +1421,105 @@ impl<T: InvokeUiSession> Remote<T> {
if digest.is_upload {
if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) {
if let Some(file) = job.files().get(digest.file_num as usize) {
let read_path = get_string(&job.join(&file.name));
let overwrite_strategy = job.default_overwrite_strategy();
if let Some(overwrite) = overwrite_strategy {
let req = FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else {
file_transfer_send_confirm_request::Union::Skip(
true,
)
}),
..Default::default()
};
job.confirm(&req);
let msg = new_send_confirm(req);
allow_err!(peer.send(&msg).await);
} else {
self.handler.override_file_confirm(
digest.id,
digest.file_num,
read_path,
true,
digest.is_identical,
);
if let fs::DataSource::FilePath(p) = &job.data_source {
let read_path =
get_string(&fs::TransferJob::join(p, &file.name));
let overwrite_strategy =
job.default_overwrite_strategy();
if let Some(overwrite) = overwrite_strategy {
let req = FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else {
file_transfer_send_confirm_request::Union::Skip(
true,
)
}),
..Default::default()
};
job.confirm(&req);
let msg = new_send_confirm(req);
allow_err!(peer.send(&msg).await);
} else {
self.handler.override_file_confirm(
digest.id,
digest.file_num,
read_path,
true,
digest.is_identical,
);
}
}
}
}
} else {
if let Some(job) = fs::get_job(digest.id, &mut self.write_jobs) {
if let Some(file) = job.files().get(digest.file_num as usize) {
let write_path = get_string(&job.join(&file.name));
let overwrite_strategy = job.default_overwrite_strategy();
match fs::is_write_need_confirmation(&write_path, &digest) {
Ok(res) => match res {
DigestCheckResult::IsSame => {
let req = FileTransferSendConfirmRequest {
if let fs::DataSource::FilePath(p) = &job.data_source {
let write_path =
get_string(&fs::TransferJob::join(p, &file.name));
let overwrite_strategy =
job.default_overwrite_strategy();
match fs::is_write_need_confirmation(
&write_path,
&digest,
) {
Ok(res) => match res {
DigestCheckResult::IsSame => {
let req = FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(file_transfer_send_confirm_request::Union::Skip(true)),
..Default::default()
};
job.confirm(&req);
let msg = new_send_confirm(req);
allow_err!(peer.send(&msg).await);
}
DigestCheckResult::NeedConfirm(digest) => {
if let Some(overwrite) = overwrite_strategy {
let req = FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else {
file_transfer_send_confirm_request::Union::Skip(true)
}),
..Default::default()
};
job.confirm(&req);
let msg = new_send_confirm(req);
allow_err!(peer.send(&msg).await);
} else {
self.handler.override_file_confirm(
digest.id,
digest.file_num,
write_path,
false,
digest.is_identical,
);
}
}
DigestCheckResult::NoSuchFile => {
let req = FileTransferSendConfirmRequest {
DigestCheckResult::NeedConfirm(digest) => {
if let Some(overwrite) = overwrite_strategy
{
let req =
FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else {
file_transfer_send_confirm_request::Union::Skip(true)
}),
..Default::default()
};
job.confirm(&req);
let msg = new_send_confirm(req);
allow_err!(peer.send(&msg).await);
} else {
self.handler.override_file_confirm(
digest.id,
digest.file_num,
write_path,
false,
digest.is_identical,
);
}
}
DigestCheckResult::NoSuchFile => {
let req = FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
..Default::default()
};
job.confirm(&req);
let msg = new_send_confirm(req);
allow_err!(peer.send(&msg).await);
job.confirm(&req);
let msg = new_send_confirm(req);
allow_err!(peer.send(&msg).await);
}
},
Err(err) => {
println!("error receiving digest: {}", err);
}
},
Err(err) => {
println!("error receiving digest: {}", err);
}
}
}
@@ -1499,23 +1531,56 @@ impl<T: InvokeUiSession> Remote<T> {
if let Err(_err) = job.write(block).await {
// to-do: add "skip" for writing job
}
self.update_jobs_status();
if job.r#type == fs::JobType::Generic {
self.update_jobs_status();
}
}
}
Some(file_response::Union::Done(d)) => {
let mut err: Option<String> = None;
if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) {
let mut job_type = fs::JobType::Generic;
let mut printer_data = None;
if let Some(job) = fs::remove_job(d.id, &mut self.write_jobs) {
job.modify_time();
err = job.job_error();
fs::remove_job(d.id, &mut self.write_jobs);
job_type = job.r#type;
printer_data = job.get_buf_data();
}
match job_type {
fs::JobType::Generic => {
self.handle_job_status(d.id, d.file_num, err);
}
fs::JobType::Printer =>
{
#[cfg(target_os = "windows")]
if let Some(data) = printer_data {
let printer_name = self
.handler
.printer_names
.write()
.unwrap()
.remove(&d.id);
crate::platform::send_raw_data_to_printer(
printer_name,
data,
)
.ok();
}
}
}
self.handle_job_status(d.id, d.file_num, err);
}
Some(file_response::Union::Error(e)) => {
if let Some(_job) = fs::get_job(e.id, &mut self.write_jobs) {
fs::remove_job(e.id, &mut self.write_jobs);
let job_type = fs::remove_job(e.id, &mut self.write_jobs)
.map(|j| j.r#type)
.unwrap_or(fs::JobType::Generic);
match job_type {
fs::JobType::Generic => {
self.handle_job_status(e.id, e.file_num, Some(e.error));
}
fs::JobType::Printer => {
log::error!("Printer job error: {}", e.error);
}
}
self.handle_job_status(e.id, e.file_num, Some(e.error));
}
_ => {}
}
@@ -1739,6 +1804,41 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
Some(message::Union::FileAction(action)) => match action.union {
Some(file_action::Union::Send(_s)) => match _s.file_type.enum_value() {
#[cfg(target_os = "windows")]
Ok(file_transfer_send_request::FileType::Printer) => {
#[cfg(feature = "flutter")]
let action = LocalConfig::get_option(
config::keys::OPTION_PRINTER_INCOMING_JOB_ACTION,
);
#[cfg(not(feature = "flutter"))]
let action = "";
if action == "dismiss" {
// Just ignore the incoming print job.
} else {
let id = fs::get_next_job_id();
#[cfg(feature = "flutter")]
let allow_auto_print = LocalConfig::get_bool_option(
config::keys::OPTION_PRINTER_ALLOW_AUTO_PRINT,
);
#[cfg(not(feature = "flutter"))]
let allow_auto_print = false;
if allow_auto_print {
let printer_name = if action == "" {
"".to_string()
} else {
LocalConfig::get_option(
config::keys::OPTION_PRINTER_SELECTED_NAME,
)
};
self.handler.printer_response(id, _s.path, printer_name);
} else {
self.handler.printer_request(id, _s.path);
}
}
}
_ => {}
},
Some(file_action::Union::SendConfirm(c)) => {
if let Some(job) = fs::get_job(c.id, &mut self.read_jobs) {
job.confirm(&c);
@@ -2004,7 +2104,7 @@ impl<T: InvokeUiSession> Remote<T> {
async fn handle_cliprdr_msg(
&mut self,
clip: hbb_common::message_proto::Cliprdr,
peer: &mut Stream,
_peer: &mut Stream,
) {
log::debug!("handling cliprdr msg from server peer");
#[cfg(feature = "flutter")]
@@ -2074,7 +2174,7 @@ impl<T: InvokeUiSession> Remote<T> {
}
if let Some(msg) = out_msg {
allow_err!(peer.send(&msg).await);
allow_err!(_peer.send(&msg).await);
}
}
}

View File

@@ -139,6 +139,10 @@ pub fn is_support_file_copy_paste_num(ver: i64) -> bool {
ver >= hbb_common::get_version_number("1.3.8")
}
pub fn is_support_remote_print(ver: &str) -> bool {
hbb_common::get_version_number(ver) >= hbb_common::get_version_number("1.3.9")
}
pub fn is_support_file_paste_if_macos(ver: &str) -> bool {
hbb_common::get_version_number(ver) >= hbb_common::get_version_number("1.3.9")
}

View File

@@ -196,12 +196,11 @@ pub fn core_main() -> Option<Vec<String>> {
if config::is_disable_installation() {
return None;
}
let res = platform::install_me(
"desktopicon startmenu",
"".to_owned(),
true,
args.len() > 1,
);
#[cfg(not(windows))]
let options = "desktopicon startmenu";
#[cfg(windows)]
let options = "desktopicon startmenu printer";
let res = platform::install_me(options, "".to_owned(), true, args.len() > 1);
let text = match res {
Ok(_) => translate("Installation Successful!".to_string()),
Err(err) => {

View File

@@ -1059,6 +1059,14 @@ impl InvokeUiSession for FlutterHandler {
fn update_record_status(&self, start: bool) {
self.push_event("record_status", &[("start", &start.to_string())], &[]);
}
fn printer_request(&self, id: i32, path: String) {
self.push_event(
"printer_request",
&[("id", json!(id)), ("path", json!(path))],
&[],
);
}
}
impl FlutterHandler {

View File

@@ -624,7 +624,15 @@ pub fn session_send_files(
_is_dir: bool,
) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.send_files(act_id, path, to, file_num, include_hidden, is_remote);
session.send_files(
act_id,
fs::JobType::Generic.into(),
path,
to,
file_num,
include_hidden,
is_remote,
);
}
}
@@ -749,7 +757,15 @@ pub fn session_add_job(
is_remote: bool,
) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.add_job(act_id, path, to, file_num, include_hidden, is_remote);
session.add_job(
act_id,
fs::JobType::Generic.into(),
path,
to,
file_num,
include_hidden,
is_remote,
);
}
}
@@ -1668,6 +1684,17 @@ pub fn session_toggle_virtual_display(session_id: SessionID, index: i32, on: boo
}
}
pub fn session_printer_response(
session_id: SessionID,
id: i32,
path: String,
printer_name: String,
) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.printer_response(id, path, printer_name);
}
}
pub fn main_set_home_dir(_home: String) {
#[cfg(any(target_os = "android", target_os = "ios"))]
{
@@ -2362,6 +2389,68 @@ pub fn main_audio_support_loopback() -> SyncReturn<bool> {
SyncReturn(is_surpport)
}
pub fn main_get_printer_names() -> SyncReturn<String> {
#[cfg(target_os = "windows")]
return SyncReturn(
serde_json::to_string(&crate::platform::windows::get_printer_names().unwrap_or_default())
.unwrap_or_default(),
);
#[cfg(not(target_os = "windows"))]
return SyncReturn("".to_owned());
}
pub fn main_get_common(key: String) -> String {
if key == "is-printer-installed" {
#[cfg(target_os = "windows")]
{
return match remote_printer::is_rd_printer_installed(&get_app_name()) {
Ok(r) => r.to_string(),
Err(e) => e.to_string(),
};
}
#[cfg(not(target_os = "windows"))]
return false.to_string();
} else if key == "is-support-printer-driver" {
#[cfg(target_os = "windows")]
return crate::platform::is_win_10_or_greater().to_string();
#[cfg(not(target_os = "windows"))]
return false.to_string();
} else if key == "transfer-job-id" {
return hbb_common::fs::get_next_job_id().to_string();
} else {
"".to_owned()
}
}
pub fn main_get_common_sync(key: String) -> SyncReturn<String> {
SyncReturn(main_get_common(key))
}
pub fn main_set_common(_key: String, _value: String) {
#[cfg(target_os = "windows")]
if _key == "install-printer" && crate::platform::is_win_10_or_greater() {
std::thread::spawn(move || {
let (success, msg) = match remote_printer::install_update_printer(&get_app_name()) {
Ok(_) => (true, "".to_owned()),
Err(e) => {
let err = e.to_string();
log::error!("Failed to install/update rd printer: {}", &err);
(false, err)
}
};
let data = HashMap::from([
("name", serde_json::json!("install-printer-res")),
("success", serde_json::json!(success)),
("msg", serde_json::json!(msg)),
]);
let _res = flutter::push_global_event(
flutter::APP_TYPE_MAIN,
serde_json::ser::to_string(&data).unwrap_or("".to_owned()),
);
});
}
}
#[cfg(target_os = "android")]
pub mod server_side {
use hbb_common::{config, log};

View File

@@ -272,6 +272,8 @@ pub enum Data {
HwCodecConfig(Option<String>),
RemoveTrustedDevices(Vec<Bytes>),
ClearTrustedDevices,
#[cfg(all(target_os = "windows", feature = "flutter"))]
PrinterData(Vec<u8>),
}
#[tokio::main(flavor = "current_thread")]
@@ -461,7 +463,7 @@ async fn handle(data: Data, stream: &mut Connection) {
.lock()
.unwrap()
.iter()
.filter(|x| x.1 == crate::server::AuthConnType::Remote)
.filter(|x| x.conn_type == crate::server::AuthConnType::Remote)
.count();
allow_err!(stream.send(&Data::VideoConnCount(Some(n))).await);
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "没有摄像头"),
("d3d_render_tip", "当启用 D3D 渲染时,某些机器可能无法显示远程画面。"),
("Use D3D rendering", "使用 D3D 渲染"),
("Printer", "打印机"),
("printer-os-requirement-tip", "打印机的传出功能需要 Windows 10 或更高版本。"),
("printer-requires-installed-{}-client-tip", "请先安装 {} 客户端。"),
("printer-{}-not-installed-tip", "未安装 {} 打印机。"),
("printer-{}-ready-tip", "{} 打印机已安装,您可以使用打印功能了。"),
("Install {} Printer", "安装 {} 打印机"),
("Outgoing Print Jobs", "传出的打印任务"),
("Incomming Print Jobs", "传入的打印任务"),
("Incoming Print Job", "传入的打印任务"),
("use-the-default-printer-tip", "使用默认的打印机执行"),
("use-the-selected-printer-tip", "使用选择的打印机执行"),
("auto-print-tip", "使用选择的打印机自动执行"),
("print-incoming-job-confirm-tip", "您收到一个远程打印任务,您想在本地执行它吗?"),
("remote-printing-disallowed-tile-tip", "不允许远程打印"),
("remote-printing-disallowed-text-tip", "被控端的权限设置拒绝了远程打印。"),
("save-settings-tip", "保存设置"),
("dont-show-again-tip", "不再显示此信息"),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Keine Kameras"),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -241,5 +241,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("upgrade_remote_rustdesk_client_to_{}_tip", "Please upgrade the RustDesk client to version {} or newer on the remote side!"),
("view_camera_unsupported_tip", "The remote device does not support viewing the camera."),
("d3d_render_tip", "When D3D rendering is enabled, the remote control screen may be black on some machines."),
("printer-requires-installed-{}-client-tip", "In order to use remote printing, {} needs to be installed on this device."),
("printer-os-requirement-tip", "The printer outgoing function requires Windows 10 or higher."),
("printer-{}-not-installed-tip", "The {} Printer is not installed."),
("printer-{}-ready-tip", "The {} Printer is installed and ready to use."),
("auto-print-tip", "Print automatically using the selected printer."),
("print-incoming-job-confirm-tip", "You received a print job from remote. Do you want to execute it at your side?"),
("use-the-default-printer-tip", "Use the default printer"),
("use-the-selected-printer-tip", "Use the selected printer"),
("remote-printing-disallowed-tile-tip", "Remote Printing disallowed"),
("remote-printing-disallowed-text-tip", "The permission settings of the controlled side deny Remote Printing."),
("save-settings-tip", "Save settings"),
("dont-show-again-tip", "Don't show this again"),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "No hay cámaras"),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Aucune caméra"),
("d3d_render_tip", "Sur certaines machines, lécran du contrôle à distance peut rester noir lors de lutilisation du rendu D3D."),
("Use D3D rendering", "Utiliser le rendu D3D"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Nessuna camera"),
("d3d_render_tip", "Quando è abilitato il rendering D3D, in alcuni computer la schermata del telecomando potrebbe essere nera."),
("Use D3D rendering", "Usa rendering D3D"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Nav kameru"),
("d3d_render_tip", "Ja ir iespējota D3D renderēšana, dažās ierīcēs tālvadības pults ekrāns var būt melns."),
("Use D3D rendering", "Izmantot D3D renderēšanu"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Geen camera's"),
("d3d_render_tip", "Wanneer D3D-rendering is ingeschakeld kan het externe scherm op sommige apparaten, zwart zijn."),
("Use D3D rendering", "Gebruik D3D-rendering"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Камера отсутствует"),
("d3d_render_tip", "При включении визуализации D3D на некоторых устройствах удалённый экран может быть чёрным."),
("Use D3D rendering", "Использовать визуализацию D3D"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "Peruna càmera"),
("d3d_render_tip", "Cando sa renderizatzione D3D est abilitada, s'ischermu de controllu remotu diat pòdere èssere nieddu in unas cantas màchinas"),
("Use D3D rendering", "Imprea sa renderizatzione D3D"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", "沒有鏡頭"),
("d3d_render_tip", "當啟用 D3D 渲染時,某些機器可能會無法顯示遠端畫面。"),
("Use D3D rendering", "使用 D3D 渲染"),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -664,5 +664,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incomming Print Jobs", ""),
("Incoming Print Job", ""),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
("print-incoming-job-confirm-tip", ""),
("remote-printing-disallowed-tile-tip", ""),
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
].iter().cloned().collect();
}

View File

@@ -1,6 +1,8 @@
#include <windows.h>
#include <wtsapi32.h>
#include <tlhelp32.h>
#include <comdef.h>
#include <xpsprint.h>
#include <cstdio>
#include <cstdint>
#include <intrin.h>
@@ -11,6 +13,7 @@
#include <versionhelpers.h>
#include <vector>
#include <sddl.h>
#include <memory>
extern "C" uint32_t get_session_user_info(PWSTR bufin, uint32_t nin, uint32_t id);
@@ -859,4 +862,114 @@ extern "C"
return isRunning;
}
} // end of extern "C"
} // end of extern "C"
// Remote printing
extern "C"
{
#pragma comment(lib, "XpsPrint.lib")
#pragma warning(push)
#pragma warning(disable : 4995)
#define PRINT_XPS_CHECK_HR(hr, msg) \
if (FAILED(hr)) \
{ \
_com_error err(hr); \
flog("%s Error: %s\n", msg, err.ErrorMessage()); \
return -1; \
}
int PrintXPSRawData(LPWSTR printerName, BYTE *rawData, ULONG dataSize)
{
BOOL isCoInitializeOk = FALSE;
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (hr == RPC_E_CHANGED_MODE)
{
hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
}
if (hr == S_OK)
{
isCoInitializeOk = TRUE;
}
std::shared_ptr<int> coInitGuard(nullptr, [isCoInitializeOk](int *) {
if (isCoInitializeOk) CoUninitialize();
});
IXpsOMObjectFactory *xpsFactory = nullptr;
hr = CoCreateInstance(
__uuidof(XpsOMObjectFactory),
nullptr,
CLSCTX_INPROC_SERVER,
__uuidof(IXpsOMObjectFactory),
reinterpret_cast<LPVOID *>(&xpsFactory));
PRINT_XPS_CHECK_HR(hr, "Failed to create XPS object factory.");
std::shared_ptr<IXpsOMObjectFactory> xpsFactoryGuard(
xpsFactory,
[](IXpsOMObjectFactory *xpsFactory) {
xpsFactory->Release();
});
HANDLE completionEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
if (completionEvent == nullptr)
{
flog("Failed to create completion event. Last error: %d\n", GetLastError());
return -1;
}
std::shared_ptr<HANDLE> completionEventGuard(
&completionEvent,
[](HANDLE *completionEvent) {
CloseHandle(*completionEvent);
});
IXpsPrintJob *job = nullptr;
IXpsPrintJobStream *jobStream = nullptr;
// `StartXpsPrintJob()` is deprecated, but we still use it for compatibility.
// We may change to use the `Print Document Package API` in the future.
// https://learn.microsoft.com/en-us/windows/win32/printdocs/xpsprint-functions
hr = StartXpsPrintJob(
printerName,
L"Print Job 1",
nullptr,
nullptr,
completionEvent,
nullptr,
0,
&job,
&jobStream,
nullptr);
PRINT_XPS_CHECK_HR(hr, "Failed to start XPS print job.");
std::shared_ptr<IXpsPrintJobStream> jobStreamGuard(jobStream, [](IXpsPrintJobStream *jobStream) {
jobStream->Release();
});
BOOL jobOk = FALSE;
std::shared_ptr<IXpsPrintJob> jobGuard(job, [&jobOk](IXpsPrintJob* job) {
if (jobOk == FALSE)
{
job->Cancel();
}
job->Release();
});
DWORD bytesWritten = 0;
hr = jobStream->Write(rawData, dataSize, &bytesWritten);
PRINT_XPS_CHECK_HR(hr, "Failed to write data to print job stream.");
hr = jobStream->Close();
PRINT_XPS_CHECK_HR(hr, "Failed to close print job stream.");
// Wait about 5 minutes for the print job to complete.
DWORD waitMillis = 300 * 1000;
DWORD waitResult = WaitForSingleObject(completionEvent, waitMillis);
if (waitResult != WAIT_OBJECT_0)
{
flog("Wait for print job completion failed. Last error: %d\n", GetLastError());
return -1;
}
jobOk = TRUE;
return 0;
}
#pragma warning(pop)
}

View File

@@ -21,23 +21,22 @@ use std::{
fs,
io::{self, prelude::*},
mem,
os::windows::process::CommandExt,
os::{raw::c_ulong, windows::process::CommandExt},
path::*,
ptr::null_mut,
sync::{atomic::Ordering, Arc, Mutex},
time::{Duration, Instant},
};
use wallpaper;
#[cfg(not(debug_assertions))]
use winapi::um::libloaderapi::{LoadLibraryExW, LOAD_LIBRARY_SEARCH_USER_DIRS};
use winapi::{
ctypes::c_void,
shared::{minwindef::*, ntdef::NULL, windef::*, winerror::*},
um::{
errhandlingapi::GetLastError,
handleapi::CloseHandle,
libloaderapi::{
GetProcAddress, LoadLibraryExA, LoadLibraryExW, LOAD_LIBRARY_SEARCH_SYSTEM32,
LOAD_LIBRARY_SEARCH_USER_DIRS,
},
libloaderapi::{GetProcAddress, LoadLibraryExA, LOAD_LIBRARY_SEARCH_SYSTEM32},
minwinbase::STILL_ACTIVE,
processthreadsapi::{
GetCurrentProcess, GetCurrentProcessId, GetExitCodeProcess, OpenProcess,
@@ -54,6 +53,10 @@ use winapi::{
TOKEN_ELEVATION, TOKEN_QUERY,
},
winreg::HKEY_CURRENT_USER,
winspool::{
EnumPrintersW, GetDefaultPrinterW, PRINTER_ENUM_CONNECTIONS, PRINTER_ENUM_LOCAL,
PRINTER_INFO_1W,
},
winuser::*,
},
};
@@ -73,6 +76,7 @@ pub const SET_FOREGROUND_WINDOW: &'static str = "SET_FOREGROUND_WINDOW";
const REG_NAME_INSTALL_DESKTOPSHORTCUTS: &str = "DESKTOPSHORTCUTS";
const REG_NAME_INSTALL_STARTMENUSHORTCUTS: &str = "STARTMENUSHORTCUTS";
const REG_NAME_INSTALL_PRINTER: &str = "PRINTER";
pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
unsafe {
@@ -1011,6 +1015,10 @@ pub fn get_install_options() -> String {
if let Some(start_menu_shortcuts) = start_menu_shortcuts {
opts.insert(REG_NAME_INSTALL_STARTMENUSHORTCUTS, start_menu_shortcuts);
}
let printer = get_reg_of_hkcr(&subkey, REG_NAME_INSTALL_PRINTER);
if let Some(printer) = printer {
opts.insert(REG_NAME_INSTALL_PRINTER, printer);
}
serde_json::to_string(&opts).unwrap_or("{}".to_owned())
}
@@ -1136,6 +1144,7 @@ fn get_after_install(
exe: &str,
reg_value_start_menu_shortcuts: Option<String>,
reg_value_desktop_shortcuts: Option<String>,
reg_value_printer: Option<String>,
) -> String {
let app_name = crate::get_app_name();
let ext = app_name.to_lowercase();
@@ -1159,12 +1168,20 @@ fn get_after_install(
)
})
.unwrap_or_default();
let reg_printer = reg_value_printer
.map(|v| {
format!(
"reg add HKEY_CLASSES_ROOT\\.{ext} /f /v {REG_NAME_INSTALL_PRINTER} /t REG_SZ /d \"{v}\""
)
})
.unwrap_or_default();
format!("
chcp 65001
reg add HKEY_CLASSES_ROOT\\.{ext} /f
{desktop_shortcuts}
{start_menu_shortcuts}
{reg_printer}
reg add HKEY_CLASSES_ROOT\\.{ext}\\DefaultIcon /f
reg add HKEY_CLASSES_ROOT\\.{ext}\\DefaultIcon /f /ve /t REG_SZ /d \"\\\"{exe}\\\",0\"
reg add HKEY_CLASSES_ROOT\\.{ext}\\shell /f
@@ -1249,6 +1266,7 @@ oLink.Save
let tray_shortcut = get_tray_shortcut(&exe, &tmp_path)?;
let mut reg_value_desktop_shortcuts = "0".to_owned();
let mut reg_value_start_menu_shortcuts = "0".to_owned();
let mut reg_value_printer = "0".to_owned();
let mut shortcuts = Default::default();
if options.contains("desktopicon") {
shortcuts = format!(
@@ -1268,6 +1286,10 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{start_menu}\\\"
);
reg_value_start_menu_shortcuts = "1".to_owned();
}
let install_printer = options.contains("printer") && crate::platform::is_win_10_or_greater();
if install_printer {
reg_value_printer = "1".to_owned();
}
let meta = std::fs::symlink_metadata(std::env::current_exe()?)?;
let size = meta.len() / 1024;
@@ -1338,7 +1360,8 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\"
after_install = get_after_install(
&exe,
Some(reg_value_start_menu_shortcuts),
Some(reg_value_desktop_shortcuts)
Some(reg_value_desktop_shortcuts),
Some(reg_value_printer)
),
sleep = if debug { "timeout 300" } else { "" },
dels = if debug { "" } else { &dels },
@@ -1346,13 +1369,22 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\"
import_config = get_import_config(&exe),
);
run_cmds(cmds, debug, "install")?;
if install_printer {
allow_err!(remote_printer::install_update_printer(
&crate::get_app_name()
));
}
run_after_run_cmds(silent);
Ok(())
}
pub fn run_after_install() -> ResultType<()> {
let (_, _, _, exe) = get_install_info();
run_cmds(get_after_install(&exe, None, None), true, "after_install")
run_cmds(
get_after_install(&exe, None, None, None),
true,
"after_install",
)
}
pub fn run_before_uninstall() -> ResultType<()> {
@@ -1413,6 +1445,9 @@ fn get_uninstall(kill_self: bool) -> String {
}
pub fn uninstall_me(kill_self: bool) -> ResultType<()> {
if crate::platform::is_win_10_or_greater() {
remote_printer::uninstall_printer(&crate::get_app_name());
}
run_cmds(get_uninstall(kill_self), true, "uninstall")
}
@@ -1570,9 +1605,18 @@ pub fn bootstrap() -> bool {
*config::EXE_RENDEZVOUS_SERVER.write().unwrap() = lic.host.clone();
}
set_safe_load_dll()
#[cfg(debug_assertions)]
{
true
}
#[cfg(not(debug_assertions))]
{
// This function will cause `'sciter.dll' was not found neither in PATH nor near the current executable.` when debugging RustDesk.
set_safe_load_dll()
}
}
#[cfg(not(debug_assertions))]
fn set_safe_load_dll() -> bool {
if !unsafe { set_default_dll_directories() } {
return false;
@@ -1589,6 +1633,7 @@ fn set_safe_load_dll() -> bool {
}
// https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-setdefaultdlldirectories
#[cfg(not(debug_assertions))]
unsafe fn set_default_dll_directories() -> bool {
let module = LoadLibraryExW(
wide_string("Kernel32.dll").as_ptr(),
@@ -2728,3 +2773,119 @@ pub mod reg_display_settings {
}
}
}
pub fn get_printer_names() -> ResultType<Vec<String>> {
let mut needed_bytes = 0;
let mut returned_count = 0;
unsafe {
// First call to get required buffer size
EnumPrintersW(
PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
std::ptr::null_mut(),
1,
std::ptr::null_mut(),
0,
&mut needed_bytes,
&mut returned_count,
);
let mut buffer = vec![0u8; needed_bytes as usize];
if EnumPrintersW(
PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
std::ptr::null_mut(),
1,
buffer.as_mut_ptr() as *mut _,
needed_bytes,
&mut needed_bytes,
&mut returned_count,
) == 0
{
return Err(anyhow!("Failed to enumerate printers"));
}
let ptr = buffer.as_ptr() as *const PRINTER_INFO_1W;
let printers = std::slice::from_raw_parts(ptr, returned_count as usize);
Ok(printers
.iter()
.filter_map(|p| {
let name = p.pName;
if !name.is_null() {
let mut len = 0;
while len < 500 {
if name.add(len).is_null() || *name.add(len) == 0 {
break;
}
len += 1;
}
if len > 0 && len < 500 {
Some(String::from_utf16_lossy(std::slice::from_raw_parts(
name, len,
)))
} else {
None
}
} else {
None
}
})
.collect())
}
}
extern "C" {
fn PrintXPSRawData(printer_name: *const u16, raw_data: *const u8, data_size: c_ulong) -> DWORD;
}
pub fn send_raw_data_to_printer(printer_name: Option<String>, data: Vec<u8>) -> ResultType<()> {
let mut printer_name = printer_name.unwrap_or_default();
if printer_name.is_empty() {
// use GetDefaultPrinter to get the default printer name
let mut needed_bytes = 0;
unsafe {
GetDefaultPrinterW(std::ptr::null_mut(), &mut needed_bytes);
}
if needed_bytes > 0 {
let mut default_printer_name = vec![0u16; needed_bytes as usize];
unsafe {
GetDefaultPrinterW(
default_printer_name.as_mut_ptr() as *mut _,
&mut needed_bytes,
);
}
printer_name = String::from_utf16_lossy(&default_printer_name[..needed_bytes as usize]);
}
} else {
if let Ok(names) = crate::platform::windows::get_printer_names() {
if !names.contains(&printer_name) {
// Don't set the first printer as current printer.
// It may not be the desired printer.
log::error!(
"Printer name \"{}\" not found, ignore the print job",
printer_name
);
bail!("Printer name \"{}\" not found", &printer_name);
}
}
}
if printer_name.is_empty() {
log::error!("Failed to get printer name");
return Err(anyhow!("Failed to get printer name"));
}
let printer_name = wide_string(&printer_name);
unsafe {
let res = PrintXPSRawData(
printer_name.as_ptr(),
data.as_ptr() as *const u8,
data.len() as c_ulong,
);
if res != 0 {
bail!("Failed to send file to printer, see logs in C:\\Windows\\temp\\test_rustdesk.log for more details.");
}
}
Ok(())
}

View File

@@ -70,6 +70,9 @@ mod service;
mod video_qos;
pub mod video_service;
#[cfg(all(target_os = "windows", feature = "flutter"))]
pub mod printer_service;
pub type Childs = Arc<Mutex<Vec<std::process::Child>>>;
type ConnMap = HashMap<i32, ConnInner>;
@@ -129,6 +132,20 @@ pub fn new() -> ServerPtr {
server.add_service(Box::new(input_service::new_window_focus()));
}
}
#[cfg(all(target_os = "windows", feature = "flutter"))]
{
match printer_service::init(&crate::get_app_name()) {
Ok(()) => {
log::info!("printer service initialized");
server.add_service(Box::new(printer_service::new(
printer_service::NAME.to_owned(),
)));
}
Err(e) => {
log::error!("printer service init failed: {}", e);
}
}
}
Arc::new(RwLock::new(server))
}

View File

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

View File

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

View File

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

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

View File

@@ -69,8 +69,6 @@ function getExt(name) {
return "";
}
var jobIdCounter = 1;
class JobTable: Reactor.Component {
this var jobs = [];
this var job_map = {};
@@ -126,8 +124,7 @@ class JobTable: Reactor.Component {
}
if (!to) return;
to += handler.get_path_sep(!is_remote) + getFileName(is_remote, path);
var id = jobIdCounter;
jobIdCounter += 1;
var id = handler.get_next_job_id();
this.jobs.push({ type: "transfer",
id: id, path: path, to: to,
include_hidden: show_hidden,
@@ -135,7 +132,7 @@ class JobTable: Reactor.Component {
is_last: false
});
this.job_map[id] = this.jobs[this.jobs.length - 1];
handler.send_files(id, path, to, 0, show_hidden, is_remote);
handler.send_files(id, 0, path, to, 0, show_hidden, is_remote);
var self = this;
self.timer(30ms, function() { self.update(); });
}
@@ -147,8 +144,8 @@ class JobTable: Reactor.Component {
is_remote: is_remote, is_last: true, file_num: file_num };
this.jobs.push(job);
this.job_map[id] = this.jobs[this.jobs.length - 1];
jobIdCounter = id + 1;
handler.add_job(id, path, to, file_num, show_hidden, is_remote);
handler.update_next_job_id(id + 1);
handler.add_job(id, 0, path, to, file_num, show_hidden, is_remote);
stdout.println(JSON.stringify(job));
}
@@ -162,16 +159,14 @@ class JobTable: Reactor.Component {
}
function addDelDir(path, is_remote) {
var id = jobIdCounter;
jobIdCounter += 1;
var id = handler.get_next_job_id();
this.jobs.push({ type: "del-dir", id: id, path: path, is_remote: is_remote });
this.job_map[id] = this.jobs[this.jobs.length - 1];
this.update();
}
function addDelFile(path, is_remote) {
var id = jobIdCounter;
jobIdCounter += 1;
var id = handler.get_next_job_id();
this.jobs.push({ type: "del-file", id: id, path: path, is_remote: is_remote });
this.job_map[id] = this.jobs[this.jobs.length - 1];
this.update();
@@ -552,9 +547,9 @@ class FolderView : Reactor.Component {
return;
}
var path = me.joinPath(name);
handler.create_dir(jobIdCounter, path, me.is_remote);
create_dir_jobs[jobIdCounter] = { is_remote: me.is_remote, path: path };
jobIdCounter += 1;
var id = handler.get_next_job_id();
handler.create_dir(id, path, me.is_remote);
create_dir_jobs[id] = { is_remote: me.is_remote, path: path };
});
}

View File

@@ -317,6 +317,9 @@ class MsgboxComponent: Reactor.Component {
if (this.type == "multiple-sessions-nocancel") {
values.sid = (this.$$(select))[0].value;
}
if (this.type == "remote-printer-selector") {
values.name = (this.$$(select))[0].value;
}
return values;
}

41
src/ui/printer.tis Normal file
View File

@@ -0,0 +1,41 @@
include "sciter:reactor.tis";
handler.printerRequest = function(id, path) {
show_printer_selector(id, path);
};
function show_printer_selector(id, path)
{
var names = handler.get_printer_names();
msgbox("remote-printer-selector", "Incoming Print Job", <PrinterComponent names={names} />, "", function(res=null) {
if (res && res.name) {
handler.on_printer_selected(id, path, res.name);
}
}, 180);
}
class PrinterComponent extends Reactor.Component {
this var names = [];
this var jobTip = translate("print-incoming-job-confirm-tip");
function this(params) {
if (params && params.names) {
this.names = params.names;
}
}
function render() {
return <div>
<div>{translate("print-incoming-job-confirm-tip")}</div>
<div style="margin-top: 1em;" />
<div>
<select style="width: 450; margin: 1em 0; font-size: 1.15em;">
{this.names.map(function(name) {
return <option value={name}>{name}</option>;
})}
</select>
</div>
<div style="margin-top: 1em;" />
</div>;
}
}

View File

@@ -15,6 +15,7 @@
include "port_forward.tis";
include "grid.tis";
include "header.tis";
include "printer.tis";
</script>
</head>
<header>

View File

@@ -379,6 +379,10 @@ impl InvokeUiSession for SciterHandler {
fn update_record_status(&self, start: bool) {
self.call("updateRecordStatus", &make_args!(start));
}
fn printer_request(&self, id: i32, path: String) {
self.call("printerRequest", &make_args!(id, path));
}
}
pub struct SciterSession(Session<SciterHandler>);
@@ -491,6 +495,8 @@ impl sciter::EventHandler for SciterSession {
fn get_chatbox();
fn get_icon();
fn get_home_dir();
fn get_next_job_id();
fn update_next_job_id(i32);
fn read_dir(String, bool);
fn remove_dir(i32, String, bool);
fn create_dir(i32, String, bool);
@@ -502,8 +508,8 @@ impl sciter::EventHandler for SciterSession {
fn confirm_delete_files(i32, i32);
fn set_no_confirm(i32);
fn cancel_job(i32);
fn send_files(i32, String, String, i32, bool, bool);
fn add_job(i32, String, String, i32, bool, bool);
fn send_files(i32, i32, String, String, i32, bool, bool);
fn add_job(i32, i32, String, String, i32, bool, bool);
fn resume_job(i32, bool);
fn get_platform(bool);
fn get_path_sep(bool);
@@ -541,6 +547,8 @@ impl sciter::EventHandler for SciterSession {
fn set_selected_windows_session_id(String);
fn is_recording();
fn has_file_clipboard();
fn get_printer_names();
fn on_printer_selected(i32, String, String);
}
}
@@ -842,6 +850,22 @@ impl SciterSession {
fn version_cmp(&self, v1: String, v2: String) -> i32 {
(hbb_common::get_version_number(&v1) - hbb_common::get_version_number(&v2)) as i32
}
fn get_printer_names(&self) -> Value {
#[cfg(target_os = "windows")]
let printer_names = crate::platform::windows::get_printer_names().unwrap_or_default();
#[cfg(not(target_os = "windows"))]
let printer_names: Vec<String> = vec![];
let mut v = Value::array(0);
for name in printer_names {
v.push(name);
}
v
}
fn on_printer_selected(&self, id: i32, path: String, printer_name: String) {
self.printer_response(id, path, printer_name);
}
}
pub fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {

View File

@@ -744,6 +744,8 @@ async fn handle_fs(
tx: &UnboundedSender<Data>,
tx_log: Option<&UnboundedSender<String>>,
) {
use std::path::PathBuf;
use hbb_common::fs::serialize_transfer_job;
match fs {
@@ -785,8 +787,9 @@ async fn handle_fs(
// dummy remote, show_hidden, is_remote
let mut job = fs::TransferJob::new_write(
id,
fs::JobType::Generic,
"".to_string(),
path,
fs::DataSource::FilePath(PathBuf::from(&path)),
file_num,
false,
false,
@@ -805,27 +808,24 @@ async fn handle_fs(
write_jobs.push(job);
}
ipc::FS::CancelWrite { id } => {
if let Some(job) = fs::get_job(id, write_jobs) {
if let Some(job) = fs::remove_job(id, write_jobs) {
job.remove_download_file();
tx_log.map(|tx: &UnboundedSender<String>| {
tx.send(serialize_transfer_job(job, false, true, ""))
tx.send(serialize_transfer_job(&job, false, true, ""))
});
fs::remove_job(id, write_jobs);
}
}
ipc::FS::WriteDone { id, file_num } => {
if let Some(job) = fs::get_job(id, write_jobs) {
if let Some(job) = fs::remove_job(id, write_jobs) {
job.modify_time();
send_raw(fs::new_done(id, file_num), tx);
tx_log.map(|tx| tx.send(serialize_transfer_job(job, true, false, "")));
fs::remove_job(id, write_jobs);
tx_log.map(|tx| tx.send(serialize_transfer_job(&job, true, false, "")));
}
}
ipc::FS::WriteError { id, file_num, err } => {
if let Some(job) = fs::get_job(id, write_jobs) {
tx_log.map(|tx| tx.send(serialize_transfer_job(job, false, false, &err)));
if let Some(job) = fs::remove_job(id, write_jobs) {
tx_log.map(|tx| tx.send(serialize_transfer_job(&job, false, false, &err)));
send_raw(fs::new_error(job.id(), err, file_num), tx);
fs::remove_job(job.id(), write_jobs);
}
}
ipc::FS::WriteBlock {
@@ -871,32 +871,34 @@ async fn handle_fs(
..Default::default()
};
if let Some(file) = job.files().get(file_num as usize) {
let path = get_string(&job.join(&file.name));
match is_write_need_confirmation(&path, &digest) {
Ok(digest_result) => {
match digest_result {
DigestCheckResult::IsSame => {
req.set_skip(true);
let msg_out = new_send_confirm(req);
send_raw(msg_out, &tx);
}
DigestCheckResult::NeedConfirm(mut digest) => {
// upload to server, but server has the same file, request
digest.is_upload = is_upload;
let mut msg_out = Message::new();
let mut fr = FileResponse::new();
fr.set_digest(digest);
msg_out.set_file_response(fr);
send_raw(msg_out, &tx);
}
DigestCheckResult::NoSuchFile => {
let msg_out = new_send_confirm(req);
send_raw(msg_out, &tx);
if let fs::DataSource::FilePath(p) = &job.data_source {
let path = get_string(&fs::TransferJob::join(p, &file.name));
match is_write_need_confirmation(&path, &digest) {
Ok(digest_result) => {
match digest_result {
DigestCheckResult::IsSame => {
req.set_skip(true);
let msg_out = new_send_confirm(req);
send_raw(msg_out, &tx);
}
DigestCheckResult::NeedConfirm(mut digest) => {
// upload to server, but server has the same file, request
digest.is_upload = is_upload;
let mut msg_out = Message::new();
let mut fr = FileResponse::new();
fr.set_digest(digest);
msg_out.set_file_response(fr);
send_raw(msg_out, &tx);
}
DigestCheckResult::NoSuchFile => {
let msg_out = new_send_confirm(req);
send_raw(msg_out, &tx);
}
}
}
}
Err(err) => {
send_raw(fs::new_error(id, err, file_num), &tx);
Err(err) => {
send_raw(fs::new_error(id, err, file_num), &tx);
}
}
}
}

View File

@@ -58,6 +58,7 @@ pub struct Session<T: InvokeUiSession> {
pub server_clipboard_enabled: Arc<RwLock<bool>>,
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
pub connection_round_state: Arc<Mutex<ConnectionRoundState>>,
pub printer_names: Arc<RwLock<HashMap<i32, String>>>,
}
#[derive(Clone)]
@@ -1505,6 +1506,20 @@ impl<T: InvokeUiSession> Session<T> {
pub fn get_conn_token(&self) -> Option<String> {
self.lc.read().unwrap().get_conn_token()
}
pub fn printer_response(&self, id: i32, path: String, printer_name: String) {
self.printer_names.write().unwrap().insert(id, printer_name);
let to = std::env::temp_dir().join(format!("rustdesk_printer_{id}"));
self.send(Data::SendFiles((
id,
hbb_common::fs::JobType::Printer,
path,
to.to_string_lossy().to_string(),
0,
false,
true,
)));
}
}
pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
@@ -1570,6 +1585,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn is_multi_ui_session(&self) -> bool;
fn update_record_status(&self, start: bool);
fn update_empty_dirs(&self, _res: ReadEmptyDirsResponse) {}
fn printer_request(&self, id: i32, path: String);
}
impl<T: InvokeUiSession> Deref for Session<T> {