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:
Hakan Seven committed 2026-08-26 00:00:23 +03:00
1 parent 2ed4a5d8f3
commit 9a8f29ae45
2 files changed
+52 -27

No files matched your search

+7 -13
View File
@@ -2963,13 +2963,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
};
let plot_style = self.dialog_plot_style(&dialog);
let options = self.plot_print_options(&dialog, Default::default());
let stamp = std::time::SystemTime::now()
.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"
));
let temp_path = crate::io::print_to_printer::temp_pdf_path("print_all");
self.save_config();
self.close_active_modal();
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_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,
// whatever the output target is.
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 work = move || {
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).
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 opts = self.plot_print_options(&d, wgroup_splits);
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_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) =
self.layout_plot_params();
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 work = move || {
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_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
+45 -14
View File
@@ -13,6 +13,22 @@ use crate::io::plot_style::PlotStyleTable;
use crate::scene::model::hatch_model::HatchModel;
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`
/// flags / `-o` options. On Windows the generated PDF already carries render
/// 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>,
opts: PrintOptions,
) -> 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(
&wires,
&hatches,
@@ -228,10 +244,15 @@ fn dispatch_to_printer_opts(
) -> Result<String, String> {
#[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::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 path_wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
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());
for _ in 0..opts.copies.max(1) {
let result = unsafe {
windows_sys::Win32::UI::Shell::ShellExecuteW(
std::ptr::null_mut(),
verb.as_ptr(),
path_wide.as_ptr(),
params_ptr,
std::ptr::null(),
windows_sys::Win32::UI::WindowsAndMessaging::SW_HIDE,
) as usize
let mut info = SHELLEXECUTEINFOW {
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32,
fMask: SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC,
lpVerb: verb.as_ptr(),
lpFile: path_wide.as_ptr(),
lpParameters: params_ptr,
nShow: SW_HIDE,
..Default::default()
};
if result <= 32 {
return Err(format!("ShellExecute failed (code {result})"));
if unsafe { ShellExecuteExW(&mut info) } == 0 {
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)