Feat: Introducing Printing on Windows - #21988
Conversation
…iation correction in printing.c
Working win32_print.c
…n the gui from print settings and other programmatic sources.
Address compile errors
…ausing pull request test failure
|
Some questions I have to ask:
I don't want to be rude, I just want to be sure that if I review it will get fixed. A similar PR was proposed and never finished. My time is precious and not extensible :) Last but not least, I'm not a Windows user but I'd love to see this missing feature integrated. TIA |
There was a problem hiding this comment.
Pull request overview
Adds native Windows printing with printer discovery, DEVMODE/PrintTicket synchronization, color-managed XPS jobs, and Windows build integration.
Changes:
- Implements Windows printer discovery and XPS printing.
- Integrates Windows print settings and ICC color management.
- Updates shared printing structures and CMake configuration.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Final review findings |
|---|---|
src/win/win32_print.h |
Critical (2 votes): Exposes C bool without including <stdbool.h>. |
src/win/win32_print.c |
Critical: Unsafe shutdown can free printer data while jobs/callbacks use it (3); XPS failures and completion status are ignored (3); image creation/placement failures are ignored (2); CreatePartUri failure can cause a null dereference (2); 16-bit ICC buffers are exposed as 24-bit RGB (1); total_written may trigger -Werror (1).Moderate: Unbalanced printer references leak (3); resolution selection is overwritten (2); paper_ids leaks (3); printer settings use inconsistent keys (3); discovery completion can cause a 40-second quit delay (2).Nit (2): wprinter leaks when OpenPrinterW fails. |
src/views/CMakeLists.txt |
No final findings. |
src/libs/print_settings.c |
Critical: Non-Windows compilation breaks due to waiting_for_printer (2), an unused local under -Werror (1), and uninitialized widget pointers used by async paths (3).Moderate: Failed print jobs are still tagged as successful (2); GlobalSize() copies excess PrintTicket data (2). |
src/libs/CMakeLists.txt |
No final findings. |
src/common/printing.c |
No final findings. |
src/common/cups_print.h |
Critical (3 votes): Windows-only WORD in the shared header breaks non-Windows builds. |
src/CMakeLists.txt |
No final findings. |
CMakeLists.txt |
No final findings. |
Suppressed comments (15)
src/libs/print_settings.c:3019
waiting_for_printeris declared only inside the_WIN32part ofdt_lib_print_settings_t, but this assignment is unconditional. Non-Windows builds therefore reference a nonexistent struct member (and cannot compile). Keep this initialization inside an_WIN32guard.
d->prt.page.margin_top = _to_mm(d, top_b);
src/libs/print_settings.c:1206
self->widgetis the module's vertical container, not the printer combobox (ps->printers). Consequently this never reads the selected printer; during initial discoverywaiting_for_printeris unset, so the ready callback skips_set_printerand the default printer's paper/settings are never populated.
self->gui_update(self);
src/libs/print_settings.c:1194
- When a printer is selected,
_set_printerhas just chosen the persisted/available paper, but this call replacesps->prt.paperwith whatever paper the driver's default DEVMODE contains._sync_print_widgets_from_pinfothen selects that driver default, so a saved paper choice (for example A4 versus Letter) is silently discarded on every printer initialization.
g_strcmp0(ps->waiting_for_printer, pinfo->name) == 0) ||
(current && g_strcmp0(current, pinfo->name) == 0))
{
_set_printer(self, pinfo->name);
src/libs/print_settings.c:3483
- The Windows initialization calls
_rebuild_image_profile_combobefore loadingPRINT_CONFIG_PREFIX "icctype"/iccprofileintod->v_icctypeandd->v_iccprofile(the non-Windows initialization that does this is skipped). In unrestricted mode the uninitialized state normally fails the search and this code resets the saved image profile toDT_COLORSPACE_NONE, changing color management on startup.
combo_idx = n;
src/libs/print_settings.c:673
- A failed
dt_win_print_file()is only logged here, after which_print_job_runcontinues to add thedarktable|printed|...tag and print timestamp. Images are therefore recorded as printed even when Windows submission fails; return the job as failed (while freeingicc_data) or skip the tagging block.
dt_control_log(_("failed to create temporary PDF for printing"));
dt_print(DT_DEBUG_ALWAYS, "failed to create temporary PDF for printing");
return 1;
}
close(fd);
src/libs/print_settings.c:868
- After importing the DEVMODE, this path only queues a redraw for the side-panel widget. The central print view is not updated with the new paper, orientation, resolution, or hardware margins, and the programmatic combobox changes are explicitly not guaranteed to emit callbacks. The preview can therefore remain stale until another action; refresh
dt_view_print_settings/the center view after the sync.
size_t *out_size)
{
*out_data = NULL;
src/libs/print_settings.c:1009
dt_win_open_printer_settings()returns FALSE when the user cancels, but this line marks the context as opened and the handler then queues the print job anyway. On the first print, clicking Cancel in the printer-properties dialog therefore still sends the job using the pre-dialog settings. Abort the print when the dialog is not confirmed, and only mark the context opened after a successful update.
{
src/libs/print_settings.c:868
- A Cancel result is ignored here: the driver may have modified the output DEVMODE before returning, and those changes are copied into the print UI even though the user cancelled. Only synchronize the DEVMODE and widgets when
dt_win_open_printer_settings()returns TRUE.
size_t *out_size)
{
*out_data = NULL;
src/libs/print_settings.c:1213
- The ready callback saves the printer under
plugins/lighttable/print/printer, butview_enter()restoresPRINT_CONFIG_PREFIX "printer". This makes a printer selected after asynchronous loading disappear across module re-entry. SavePRINT_CONFIG_PREFIX "printer"instead.
static void _printer_changed(GtkWidget *combo, const dt_lib_module_t *self)
src/win/win32_print.c:1331
- Each export moves its allocation into
img->bufand clearsparams->buf; unlike the PDF path, this Windows loop never frees the per-box buffers after WIC has encoded them._print_job_cleanuptherefore cannot reclaim them, so every Windows print leaks all exported image data. Add cleanup for every box, including resource-construction failures and early exits.
IXpsOMImageResource *res = _win_build_image_resource(factory, box, i);
if(res)
{
_win_place_image_on_page(factory, page, res, profile_resource, box, pinfo->printer.resolution, page_size.height);
res->lpVtbl->Release(res);
src/win/win32_print.c:874
- The new
sourcemember is never initialized in either quality-list allocation path. It therefore contains indeterminate data, so any consumer using the exposeddt_win_quality_tcannot distinguish capabilities from the fallback/DEVMODE cases. Set the appropriateQUALITY_SRC_*value for each branch.
dt_win_quality_t *q = g_malloc(sizeof(*q));
q->xdpi = resolutions[i].x;
q->ydpi = resolutions[i].y;
list = g_list_append(list, q);
src/win/win32_print.c:390
- The helper says these filters are case-insensitive, but
g_strrstr()is case-sensitive. Virtual printers named with different casing (for examplefaxoronenote) will therefore appear in the print list despite the filter; normalize the name or use a case-insensitive search.
// Case-insensitive checks for common virtual devices
if(g_strrstr(name, "Fax")) return TRUE;
if(g_strrstr(name, "OneNote")) return TRUE;
if(g_strrstr(name, "XPS")) return TRUE;
src/win/win32_print.c:1115
- When an ICC resource is supplied,
SetColorProfileResourceis what tags the already-converted image for XPS. Swallowing its failure allows an untagged image to be printed, so a selected printer profile can produce incorrect colors while the job still reports success. Treat this failure as fatal (and propagate it from the page/print-job path) rather than silently continuing.
HRESULT profile_hr = brush->lpVtbl->SetColorProfileResource(brush, profile_resource);
if(FAILED(profile_hr))
{
// deliberately not fatal — worth seeing whether output looks right
// even if this call fails, rather than aborting the whole page
}
src/win/win32_print.c:1397
Close()does not release a COM interface reference. The document and print-ticket streams returned byStartXpsPrintJobare closed here but neverReleased, leaking two COM objects for every print job. Release each stream after closing it.
WaitForSingleObject(completionEvent, INFINITE);
src/win/win32_print.c:1399
- The completion wait is
INFINITE, so an offline/unresponsive printer or spooler can leave the user export worker blocked indefinitely and prevent clean shutdown. Use a bounded wait and cancel/close the XPS job on timeout, while reporting the timeout as a failed print.
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| DWORD needed = 0, returned = 0; | ||
| (void)EnumPrintersW(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, NULL, 2, NULL, 0, &needed, &returned); | ||
|
|
||
| if(needed == 0) { g_free(dt_default); return 0; } |
| const UINT stride = (UINT)box->exp_width * 3; | ||
| hr = wic->lpVtbl->CreateBitmapFromMemory(wic, | ||
| box->exp_width, | ||
| box->exp_height, | ||
| &GUID_WICPixelFormat24bppRGB, |
No relationship to pull 20828. I see that pull request and they are going down the same road I did over a year ago when life got in the way. They are using GDI printing, which was my first approach as well but I found issues. First they output to a PDF and rely on an external utility to send that PDF to the printer, which is not guaranteed to be color managed - therefore corretly limited to srgb output. i also found when going to bltbit, as they not as a next step, the page setup became very complicated, print job sizes grew massively since you send a bitmap of the entire page to the printer, and therefore prints while successful were agonizingly slow. It's a fair approach though, so no disrespect to their efforts, same way I was going originally. Yes, life got in the way when I originally looked at this and put something up on PIXLS, but I never created a pull request here as life got in the way of active development on my proof of concept, therefore no formal pull request for code just an inquiry. I finally had time to do a rethink of how to handle the print job on Windows. I'm actively looking at the compile errors, particularly on the linux/macos side and addressing. Looks like I currently have a clean compile on Windows in the testing but some blockers on the linux/mac side. I'll look at the CoPilot code check comments as well. I do think once this is up and running, like the CUPS side should be reasonably maintenance free pending more universal testing across a range of actual printers. |
|
@Cjhemmer : thanks for the quick reply. Sounds good then. |
…ker behind windows only code Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Windows Printing has been lacking from Darktable. I have implemented a working print module, building upon the work already in the base code. The majority of changes occur in two files.
libs/print_settings.c which has been modified with _WIN32 guarded code except for rare instances where a change made sense for all platforms. This scaffolds onto the existing code base the functions necesary to keep the Windows print ticket and device mode (DEVMODE) up to date as settings are changed.
The other is a new file, win/win32_print.c (along with its header) which attempts to mirror the functionality of common/cups_print.c. cups_print.c is not included in the cmake though, only the header to cover compiling the cross platform print_settings. It handles printer discovery, DEVMODE enumeration, and print job creation.
CMake files are modified to enable the Print module on a WIndows build.
A minor edit was also made to instantiate pos in printing.c as it was throwing a compile error on in MSYS2.
A minor edit was made to cups_print.h to add a paper_id variable that is specific to my Windows structure but might be useful for all platforms.
I have tested this with a modern Epson inkjet, older Canon laser all in one, and Microsoft Print to PDF with success. It leverages the XPS print API for print job creation, so very old printers may not work, but i found xps to be far faster than GDI printing. I only have physical access to the two listed to test.
Print pipeline is fully color managed and prints directly to the printer, not relying on an external utility which may or may not respectcembedded color profiles, like PDF printing does on Windows.