diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 4cf472e7c..53162afdd 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -2963,13 +2963,7 @@ pub(super) fn on_open_file(&mut self) -> Task { }; 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 { .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 { // 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 { // 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 { .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 { .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 diff --git a/src/io/print_to_printer.rs b/src/io/print_to_printer.rs index 61084e33b..648e41629 100644 --- a/src/io/print_to_printer.rs +++ b/src/io/print_to_printer.rs @@ -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, opts: PrintOptions, ) -> Result { - 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 { #[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 { OsStr::new(s).encode_wide().chain(Some(0)).collect() }; let path_wide: Vec = 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::() 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)