Merge pull request #1517 from mechpanos/fix/windows-printer-properties-freeze

fix(print): open the driver's printing preferences without blocking the event loop
This commit is contained in:
Hakan Seven committed 2026-09-28 17:27:32 +03:00
commit 1a7e372f24
24 files changed
+35 -166

No files matched your search

-2
View File
@@ -5277,8 +5277,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = دمج الخطوط المتداخلة
.object-lineweights = سماكات خطوط الكائنات
-2
View File
@@ -5265,8 +5265,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Обедини припокриващи се линии
.object-lineweights = Дебелини на линии на обект
-2
View File
@@ -5262,8 +5262,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Sloučit překrývající se čáry
.object-lineweights = Tloušťky čar objektů
-2
View File
@@ -5263,8 +5263,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Zusammenführung überlappender Leitungen
.object-lineweights = Objektliniengewichte
-2
View File
@@ -5265,8 +5265,6 @@ plot =
.this-printer-reports-no-driver-options = Ο εκτυπωτής δεν αναφέρει επιλογές driver.
.could-not-read-the-printer-s-options-error = Αδυναμία ανάγνωσης των επιλογών του εκτυπωτή: __ocs_fmt_0__
.reading-the-printer-s-options = Ανάγνωση των επιλογών του εκτυπωτή…
.printing-preferences-saved-for-printer = Οι προτιμήσεις εκτύπωσης αποθηκεύτηκαν για τον εκτυπωτή __ocs_fmt_0__.
.printing-preferences-unchanged = Οι προτιμήσεις εκτύπωσης δεν άλλαξαν.
.printer-properties = Ιδιότητες εκτυπωτή
.merge-overlapping-lines = Συγχώνευση επικαλυπτόμενων γραμμών
.object-lineweights = Πάχη γραμμών αντικειμένων
-2
View File
@@ -5266,8 +5266,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Merge overlapping lines
.object-lineweights = Object lineweights
-2
View File
@@ -5265,8 +5265,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Fusionar líneas solapadas
.object-lineweights = Grosores de línea de objetos
-2
View File
@@ -5264,8 +5264,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Yhdistä päällekkäiset rivit
.object-lineweights = Objektin viivan painot
-2
View File
@@ -5265,8 +5265,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Fusionner les lignes qui se chevauchent
.object-lineweights = Poids de ligne d'objet
-2
View File
@@ -5253,8 +5253,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = मर्ज ओवरलैपिंग लाइन
.object-lineweights = ऑब्जेक्ट लाइनवेट
-2
View File
@@ -5264,8 +5264,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Egyesítse az átfedő vonalakat
.object-lineweights = Objektumvonalvastagságok
-2
View File
@@ -5264,8 +5264,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Unisci linee sovrapposte
.object-lineweights = Spessori linea degli oggetti
-2
View File
@@ -5253,8 +5253,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = 重複線を結合
.object-lineweights = オブジェクトの線の太さ
-2
View File
@@ -5264,8 +5264,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = 겹치는 선 병합
.object-lineweights = 객체 선가중치
-2
View File
@@ -5263,8 +5263,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Overlaplijnen samenvoegen
.object-lineweights = Objectlijndiktes
-2
View File
@@ -5264,8 +5264,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Połącz nakładające się linie
.object-lineweights = Szerokości linii obiektu
-2
View File
@@ -5265,8 +5265,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Mesclar linhas sobrepostas
.object-lineweights = Espessuras de linha dos objetos
-2
View File
@@ -5266,8 +5266,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Слияние пересекающихся линий
.object-lineweights = Весы для объектов
-2
View File
@@ -5205,8 +5205,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = Çakışan çizgileri birleştir
.object-lineweights = Nesne çizgi kalınlıkları
-2
View File
@@ -5248,8 +5248,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = 合并重叠线
.object-lineweights = 对象线宽
-2
View File
@@ -5260,8 +5260,6 @@ plot =
.this-printer-reports-no-driver-options = This printer reports no driver options.
.could-not-read-the-printer-s-options-error = Could not read the printer's options: __ocs_fmt_0__
.reading-the-printer-s-options = Reading the printer's options…
.printing-preferences-saved-for-printer = Printing preferences saved for __ocs_fmt_0__.
.printing-preferences-unchanged = Printing preferences unchanged.
.printer-properties = Printer properties
.merge-overlapping-lines = 合併重疊線
.object-lineweights = 物件線寬
+16 -39
View File
@@ -4416,9 +4416,9 @@ impl OpenCADStudio {
///
/// On CUPS platforms (Linux, macOS) the driver's options are listed in an
/// in-line editor and applied to every job for that printer through
/// `lp -o`. On Windows the driver's own document-properties sheet is
/// shown and its result stored as the printer's preferences, which the
/// print job honours. Without a named printer, or where the options
/// `lp -o`. On Windows the system opens the driver's own printing
/// preferences in its own process, and the print job honours what the
/// user saves there. Without a named printer, or where the options
/// cannot be listed, the platform's printer settings open instead.
fn on_printer_properties(&mut self) -> Task<Message> {
// "Default" is a real printer: its preferences sheet is the one to
@@ -4429,12 +4429,18 @@ impl OpenCADStudio {
.clone()
.or_else(|| self.plot_dialog.default_printer.clone());
let Some(printer) = chosen.filter(|_| !self.plot_dialog.to_file) else {
self.open_printer_settings_fallback();
self.open_printer_settings(None);
return Task::none();
};
#[cfg(target_os = "windows")]
{
return self.edit_windows_printer_preferences(printer);
// The driver's sheet is modal: it pumps messages itself until the
// user closes it. Hosted on our event-loop thread it starves the
// loop it is sharing, so the window stops painting and a core
// spins. The system's own preferences process pumps its own
// messages instead, which leaves ours free.
self.open_printer_settings(Some(&printer));
return Task::none();
}
#[allow(unreachable_code)]
{
@@ -4456,40 +4462,11 @@ impl OpenCADStudio {
}
}
/// The driver's document-properties sheet, owned by the main window so
/// it sits on top of the plot dialog.
#[cfg(target_os = "windows")]
fn edit_windows_printer_preferences(&mut self, printer: String) -> Task<Message> {
let Some(window) = self.main_window else {
self.open_printer_settings_fallback();
return Task::none();
};
iced::window::run(window, move |w| {
use iced::window::raw_window_handle::RawWindowHandle;
let owner = match w.window_handle().ok().map(|h| h.as_raw()) {
Some(RawWindowHandle::Win32(handle)) => handle.hwnd.get(),
_ => 0,
};
match crate::io::print_to_printer::edit_printer_preferences(&printer, owner) {
Ok(true) => Ok(crate::tf!("Printing preferences saved for {printer}.").into_owned()),
Ok(false) => Ok(crate::t!("Printing preferences unchanged.").into_owned()),
Err(error) => Err(error),
}
})
.then(|result| {
Task::done(Message::BackgroundIoFinished(
result.map(|message| message),
false,
))
})
}
/// The platform's printer settings surface: a settings panel on Linux,
/// the printers control panel on Windows, System Settings on macOS.
fn open_printer_settings_fallback(&mut self) {
match crate::io::print_to_printer::open_printer_properties(
self.plot_dialog.printer.as_deref(),
) {
/// The platform's printer settings surface: the driver's printing
/// preferences for `printer`, or — without one — a settings panel on
/// Linux, the printers control panel on Windows, System Settings on macOS.
fn open_printer_settings(&mut self, printer: Option<&str>) {
match crate::io::print_to_printer::open_printer_properties(printer) {
Ok(()) => self
.command_line
.push_info(crate::t!("Opened printer properties.").as_ref()),
+19 -83
View File
@@ -1593,6 +1593,25 @@ mod printer_properties_tests {
}
}
/// `printui` parses the raw command line itself, so a name that reaches it
/// split over several arguments matches no printer and it exits 0 without
/// drawing anything — a failure with nothing to show the user. The name
/// has to stay a single argument, directly after `/n`. The verb matters
/// too: `/e` is printing preferences, `/p` the device's admin sheet.
#[cfg(target_os = "windows")]
#[test]
fn windows_preferences_keep_the_printer_name_in_one_argument() {
for name in ["Office Printer", "Wide Format Plotter 1200", "Εκτυπωτής γραφείου"] {
let (program, args) = printer_properties_command(Some(name));
assert_eq!(program, "rundll32.exe");
assert!(args.contains(&"/e".to_string()), "{args:?} must ask for preferences");
assert!(!args.contains(&"/p".to_string()), "{args:?} must not ask for properties");
let flag = args.iter().position(|arg| arg == "/n").expect("/n introduces the name");
assert_eq!(args.get(flag + 1).map(String::as_str), Some(name), "{args:?}");
assert_eq!(args.iter().filter(|arg| arg.as_str() == name).count(), 1);
}
}
#[cfg(target_os = "macos")]
#[test]
fn macos_opens_print_settings() {
@@ -1797,89 +1816,6 @@ pub fn open_desktop_printer_settings(printer: Option<&str>) -> Result<(), String
.map_err(|error| format!("Could not open printer settings: {error}"))
}
/// Show the driver's own document-properties sheet for `printer`, owned by
/// the window `owner` (an `HWND`, 0 for none), and on OK store the result as
/// the user's printing preferences for that printer — which is what the
/// print job then honours. Returns `Ok(false)` when the user cancelled.
#[cfg(target_os = "windows")]
pub fn edit_printer_preferences(printer: &str, owner: isize) -> Result<bool, String> {
use windows_sys::Win32::Graphics::Gdi::DEVMODEW;
use windows_sys::Win32::Graphics::Printing::{
ClosePrinter, DocumentPropertiesW, OpenPrinterW, SetPrinterW, PRINTER_HANDLE,
PRINTER_INFO_9W,
};
// winspool.h: DM_OUT_BUFFER = DM_COPY, DM_IN_PROMPT = DM_PROMPT,
// DM_IN_BUFFER = DM_MODIFY.
const DM_OUT_BUFFER: u32 = 2;
const DM_IN_PROMPT: u32 = 4;
const DM_IN_BUFFER: u32 = 8;
const IDOK: i32 = 1;
let name: Vec<u16> = printer.encode_utf16().chain(std::iter::once(0)).collect();
let mut handle = PRINTER_HANDLE {
Value: std::ptr::null_mut(),
};
// SAFETY: `name` is NUL-terminated and outlives the call; a null default
// asks for PRINTER_ACCESS_USE, enough for per-user preferences.
if unsafe { OpenPrinterW(name.as_ptr(), &mut handle, std::ptr::null()) } == 0 {
return Err(format!("Could not open printer \"{printer}\""));
}
let owner = owner as windows_sys::Win32::Foundation::HWND;
let outcome = (|| {
// SAFETY: with null buffers the call only reports the DEVMODE size.
let size = unsafe {
DocumentPropertiesW(owner, handle, name.as_ptr(), std::ptr::null_mut(), std::ptr::null(), 0)
};
if size <= 0 {
return Err(format!("Printer \"{printer}\" reports no document properties"));
}
// DEVMODEW is u16-aligned; a u16 buffer keeps the cast sound.
let words = (size as usize).div_ceil(2);
let mut current = vec![0u16; words];
let mut edited = vec![0u16; words];
// SAFETY: both buffers hold `size` bytes as the driver requested.
let fetched = unsafe {
DocumentPropertiesW(
owner,
handle,
name.as_ptr(),
current.as_mut_ptr().cast::<DEVMODEW>(),
std::ptr::null(),
DM_OUT_BUFFER,
)
};
if fetched < 0 {
return Err(format!("Could not read the preferences of \"{printer}\""));
}
edited.copy_from_slice(&current);
// SAFETY: as above; DM_IN_PROMPT runs the driver's modal sheet.
let response = unsafe {
DocumentPropertiesW(
owner,
handle,
name.as_ptr(),
edited.as_mut_ptr().cast::<DEVMODEW>(),
current.as_ptr().cast::<DEVMODEW>(),
DM_IN_BUFFER | DM_IN_PROMPT | DM_OUT_BUFFER,
)
};
if response != IDOK {
return Ok(false);
}
let info = PRINTER_INFO_9W {
pDevMode: edited.as_mut_ptr().cast::<DEVMODEW>(),
};
// SAFETY: level 9 takes a PRINTER_INFO_9W; the DEVMODE outlives the call.
if unsafe { SetPrinterW(handle, 9, (&info as *const PRINTER_INFO_9W).cast::<u8>(), 0) } == 0 {
return Err(format!("Could not save the preferences of \"{printer}\""));
}
Ok(true)
})();
// SAFETY: `handle` came from OpenPrinterW above.
unsafe { ClosePrinter(handle) };
outcome
}
#[cfg(test)]
mod driver_option_tests {
use super::*;
-2
View File
@@ -1504,8 +1504,6 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"This printer reports no driver options." => Some(("plot", "this-printer-reports-no-driver-options")),
"Could not read the printer's options: {error}" => Some(("plot", "could-not-read-the-printer-s-options-error")),
"Reading the printer's options…" => Some(("plot", "reading-the-printer-s-options")),
"Printing preferences saved for {printer}." => Some(("plot", "printing-preferences-saved-for-printer")),
"Printing preferences unchanged." => Some(("plot", "printing-preferences-unchanged")),
"Printer properties" => Some(("plot", "printer-properties")),
"Layout renamed: '{old_name}' → '{new_name}'" => Some(("common", "layout-renamed-old-name-new-name")),
"Layouts" => Some(("layout", "layouts")),