fix(print): avoid Windows dispatch hangs
Use unique temporary PDFs and keep shell print handlers off the UI thread. Report missing PDF print associations explicitly. Refs #855
This commit is contained in:
parent
2ed4a5d8f3
commit
9a8f29ae45
2 changed files with 52 additions and 27 deletions
|
|
@ -2963,13 +2963,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
};
|
};
|
||||||
let plot_style = self.dialog_plot_style(&dialog);
|
let plot_style = self.dialog_plot_style(&dialog);
|
||||||
let options = self.plot_print_options(&dialog, Default::default());
|
let options = self.plot_print_options(&dialog, Default::default());
|
||||||
let stamp = std::time::SystemTime::now()
|
let temp_path = crate::io::print_to_printer::temp_pdf_path("print_all");
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.map(|duration| duration.as_nanos())
|
|
||||||
.unwrap_or(0);
|
|
||||||
let temp_path = std::env::temp_dir().join(format!(
|
|
||||||
"open_cad_studio_print_all_{stamp}.pdf"
|
|
||||||
));
|
|
||||||
self.save_config();
|
self.save_config();
|
||||||
self.close_active_modal();
|
self.close_active_modal();
|
||||||
self.command_line.push_info(
|
self.command_line.push_info(
|
||||||
|
|
@ -2987,7 +2981,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
.map(|printer| format!("Sent {} layouts to printer: {printer}", pages.len()))
|
.map(|printer| format!("Sent {} layouts to printer: {printer}", pages.len()))
|
||||||
.map_err(|error| format!("Print failed: {error}"))
|
.map_err(|error| format!("Print failed: {error}"))
|
||||||
};
|
};
|
||||||
self.run_print_all_work(dialog.background, work)
|
self.run_print_all_work(true, work)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4150,7 +4144,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
// Preview renders to a temp PDF and opens it — never a save dialog,
|
// Preview renders to a temp PDF and opens it — never a save dialog,
|
||||||
// whatever the output target is.
|
// whatever the output target is.
|
||||||
if preview {
|
if preview {
|
||||||
let tmp = std::env::temp_dir().join("open_cad_studio_preview.pdf");
|
let tmp = crate::io::print_to_printer::temp_pdf_path("preview");
|
||||||
let render_options = Self::pdf_plot_options(&d, wgroup_splits);
|
let render_options = Self::pdf_plot_options(&d, wgroup_splits);
|
||||||
let work = move || {
|
let work = move || {
|
||||||
crate::io::pdf_export::export_pdf(
|
crate::io::pdf_export::export_pdf(
|
||||||
|
|
@ -4166,7 +4160,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
// Tested clipped export (opens a save dialog).
|
// Tested clipped export (opens a save dialog).
|
||||||
return Task::done(Message::PlotWindowExport);
|
return Task::done(Message::PlotWindowExport);
|
||||||
}
|
}
|
||||||
let tmp = std::env::temp_dir().join("open_cad_studio_print.pdf");
|
let tmp = crate::io::print_to_printer::temp_pdf_path("print");
|
||||||
let render_options = Self::pdf_plot_options(&d, wgroup_splits);
|
let render_options = Self::pdf_plot_options(&d, wgroup_splits);
|
||||||
let opts = self.plot_print_options(&d, wgroup_splits);
|
let opts = self.plot_print_options(&d, wgroup_splits);
|
||||||
let work = move || {
|
let work = move || {
|
||||||
|
|
@ -4178,14 +4172,14 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
.map(|printer| format!("Sent to printer: {printer}"))
|
.map(|printer| format!("Sent to printer: {printer}"))
|
||||||
.map_err(|e| format!("Print failed: {e}"))
|
.map_err(|e| format!("Print failed: {e}"))
|
||||||
};
|
};
|
||||||
return self.run_plot_work(d.background, false, work);
|
return self.run_plot_work(true, false, work);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (wires, hatches, wipeouts, group_splits, page_w, page_h, ox, oy, rotation, scale, clip) =
|
let (wires, hatches, wipeouts, group_splits, page_w, page_h, ox, oy, rotation, scale, clip) =
|
||||||
self.layout_plot_params();
|
self.layout_plot_params();
|
||||||
|
|
||||||
if preview {
|
if preview {
|
||||||
let tmp = std::env::temp_dir().join("open_cad_studio_preview.pdf");
|
let tmp = crate::io::print_to_printer::temp_pdf_path("preview");
|
||||||
let render_options = Self::pdf_plot_options(&d, group_splits);
|
let render_options = Self::pdf_plot_options(&d, group_splits);
|
||||||
let work = move || {
|
let work = move || {
|
||||||
crate::io::pdf_export::export_pdf(
|
crate::io::pdf_export::export_pdf(
|
||||||
|
|
@ -4224,7 +4218,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
.map(|printer| format!("Sent to printer: {printer}"))
|
.map(|printer| format!("Sent to printer: {printer}"))
|
||||||
.map_err(|error| format!("Print failed: {error}"))
|
.map_err(|error| format!("Print failed: {error}"))
|
||||||
};
|
};
|
||||||
self.run_plot_work(d.background, false, work)
|
self.run_plot_work(true, false, work)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a [`PrintOptions`](crate::io::print_to_printer::PrintOptions) from
|
/// Build a [`PrintOptions`](crate::io::print_to_printer::PrintOptions) from
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,22 @@ use crate::io::plot_style::PlotStyleTable;
|
||||||
use crate::scene::model::hatch_model::HatchModel;
|
use crate::scene::model::hatch_model::HatchModel;
|
||||||
use crate::io::pdf_export::PlotWire;
|
use crate::io::pdf_export::PlotWire;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(crate) fn temp_pdf_path(kind: &str) -> std::path::PathBuf {
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||||
|
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let stamp = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.map(|duration| duration.as_nanos())
|
||||||
|
.unwrap_or(0);
|
||||||
|
std::env::temp_dir().join(format!(
|
||||||
|
"open_cad_studio_{kind}_{}_{stamp}_{id}.pdf",
|
||||||
|
std::process::id()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Extra options for a print job. On CUPS (Linux/macOS) these map to `lp`
|
/// Extra options for a print job. On CUPS (Linux/macOS) these map to `lp`
|
||||||
/// flags / `-o` options. On Windows the generated PDF already carries render
|
/// flags / `-o` options. On Windows the generated PDF already carries render
|
||||||
/// options. Windows queues repeated jobs when more than one copy is requested;
|
/// options. Windows queues repeated jobs when more than one copy is requested;
|
||||||
|
|
@ -166,7 +182,7 @@ pub async fn print_wires_with(
|
||||||
plot_style: Option<PlotStyleTable>,
|
plot_style: Option<PlotStyleTable>,
|
||||||
opts: PrintOptions,
|
opts: PrintOptions,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let tmp_path = std::env::temp_dir().join("open_cad_studio_print.pdf");
|
let tmp_path = temp_pdf_path("print");
|
||||||
pdf_export::export_pdf(
|
pdf_export::export_pdf(
|
||||||
&wires,
|
&wires,
|
||||||
&hatches,
|
&hatches,
|
||||||
|
|
@ -228,10 +244,15 @@ fn dispatch_to_printer_opts(
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
// Target a named printer via the "printto" verb; fall back to the
|
|
||||||
// default-printer "print" verb.
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::os::windows::ffi::OsStrExt;
|
use std::os::windows::ffi::OsStrExt;
|
||||||
|
use windows_sys::Win32::Foundation::{GetLastError, ERROR_NO_ASSOCIATION};
|
||||||
|
use windows_sys::Win32::UI::Shell::{
|
||||||
|
ShellExecuteExW, SHELLEXECUTEINFOW, SEE_MASK_FLAG_NO_UI, SEE_MASK_NOASYNC,
|
||||||
|
SE_ERR_NOASSOC,
|
||||||
|
};
|
||||||
|
use windows_sys::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||||
|
|
||||||
let wide = |s: &str| -> Vec<u16> { OsStr::new(s).encode_wide().chain(Some(0)).collect() };
|
let wide = |s: &str| -> Vec<u16> { OsStr::new(s).encode_wide().chain(Some(0)).collect() };
|
||||||
let path_wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
|
let path_wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
|
||||||
let (verb, params, label) = match opts.printer.as_deref() {
|
let (verb, params, label) = match opts.printer.as_deref() {
|
||||||
|
|
@ -240,18 +261,28 @@ fn dispatch_to_printer_opts(
|
||||||
};
|
};
|
||||||
let params_ptr = params.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null());
|
let params_ptr = params.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null());
|
||||||
for _ in 0..opts.copies.max(1) {
|
for _ in 0..opts.copies.max(1) {
|
||||||
let result = unsafe {
|
let mut info = SHELLEXECUTEINFOW {
|
||||||
windows_sys::Win32::UI::Shell::ShellExecuteW(
|
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32,
|
||||||
std::ptr::null_mut(),
|
fMask: SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC,
|
||||||
verb.as_ptr(),
|
lpVerb: verb.as_ptr(),
|
||||||
path_wide.as_ptr(),
|
lpFile: path_wide.as_ptr(),
|
||||||
params_ptr,
|
lpParameters: params_ptr,
|
||||||
std::ptr::null(),
|
nShow: SW_HIDE,
|
||||||
windows_sys::Win32::UI::WindowsAndMessaging::SW_HIDE,
|
..Default::default()
|
||||||
) as usize
|
|
||||||
};
|
};
|
||||||
if result <= 32 {
|
if unsafe { ShellExecuteExW(&mut info) } == 0 {
|
||||||
return Err(format!("ShellExecute failed (code {result})"));
|
let shell_code = info.hInstApp as usize;
|
||||||
|
let code = if (1..=32).contains(&shell_code) {
|
||||||
|
shell_code as u32
|
||||||
|
} else {
|
||||||
|
unsafe { GetLastError() }
|
||||||
|
};
|
||||||
|
if code == SE_ERR_NOASSOC || code == ERROR_NO_ASSOCIATION {
|
||||||
|
return Err(
|
||||||
|
"Windows has no PDF application registered with Print support.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Err(format!("Windows print dispatch failed (code {code})"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(label)
|
Ok(label)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue