Skip to content

Feat: Introducing Printing on Windows - #21988

Open
Cjhemmer wants to merge 45 commits into
darktable-org:masterfrom
Cjhemmer:master
Open

Feat: Introducing Printing on Windows#21988
Cjhemmer wants to merge 45 commits into
darktable-org:masterfrom
Cjhemmer:master

Conversation

@Cjhemmer

@Cjhemmer Cjhemmer commented Aug 25, 2026

Copy link
Copy Markdown

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.

@Cjhemmer Cjhemmer changed the title Introducing Printing on Windows Feat: Introducing Printing on Windows Aug 25, 2026
@ralfbrown ralfbrown added scope: windows support windows related issues and PR feature: new new features to add labels Aug 25, 2026
@TurboGit

Copy link
Copy Markdown
Member

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

@TurboGit TurboGit added the scope: print all cups and printing related issues label Aug 25, 2026
@TurboGit TurboGit added this to the 5.8 milestone Aug 25, 2026
@TurboGit
TurboGit requested a lite review from Copilot August 25, 2026 15:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_printer is declared only inside the _WIN32 part of dt_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 _WIN32 guard.

  d->prt.page.margin_top = _to_mm(d, top_b);

src/libs/print_settings.c:1206

  • self->widget is the module's vertical container, not the printer combobox (ps->printers). Consequently this never reads the selected printer; during initial discovery waiting_for_printer is unset, so the ready callback skips _set_printer and 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_printer has just chosen the persisted/available paper, but this call replaces ps->prt.paper with whatever paper the driver's default DEVMODE contains. _sync_print_widgets_from_pinfo then 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_combo before loading PRINT_CONFIG_PREFIX "icctype"/iccprofile into d->v_icctype and d->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 to DT_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_run continues to add the darktable|printed|... tag and print timestamp. Images are therefore recorded as printed even when Windows submission fails; return the job as failed (while freeing icc_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, but view_enter() restores PRINT_CONFIG_PREFIX "printer". This makes a printer selected after asynchronous loading disappear across module re-entry. Save PRINT_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->buf and clears params->buf; unlike the PDF path, this Windows loop never frees the per-box buffers after WIC has encoded them. _print_job_cleanup therefore 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 source member is never initialized in either quality-list allocation path. It therefore contains indeterminate data, so any consumer using the exposed dt_win_quality_t cannot distinguish capabilities from the fallback/DEVMODE cases. Set the appropriate QUALITY_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 example fax or onenote) 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, SetColorProfileResource is 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 by StartXpsPrintJob are closed here but never Released, 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.

Comment thread src/common/cups_print.h Outdated
Comment thread src/libs/print_settings.c Outdated
Comment thread src/libs/print_settings.c
Comment thread src/libs/print_settings.c
Comment thread src/win/win32_print.c
Comment thread src/win/win32_print.c
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; }
Comment thread src/win/win32_print.c
Comment thread src/win/win32_print.c
Comment thread src/win/win32_print.c
Comment on lines +1033 to +1037
const UINT stride = (UINT)box->exp_width * 3;
hr = wic->lpVtbl->CreateBitmapFromMemory(wic,
box->exp_width,
box->exp_height,
&GUID_WICPixelFormat24bppRGB,
Comment thread src/win/win32_print.h
@Cjhemmer

Copy link
Copy Markdown
Author

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

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.

@TurboGit

Copy link
Copy Markdown
Member

@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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: new new features to add scope: print all cups and printing related issues scope: windows support windows related issues and PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants