Skip to content

Feat/printer redirection - #46

Merged
dkmstr merged 10 commits into
masterfrom
feat/printer-redirection
Oct 30, 2026
Merged

dkmstr merged 10 commits into
masterfrom
feat/printer-redirection

Conversation

@dkmstr

@dkmstr dkmstr commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

No description provided.

The printer channel stub announced no printers, so nothing printed in
RDP and RDS Embedded sessions.

PrinterHandle now numbers jobs and keeps the open ones, and delegates the
OS work to a backend. The Windows backend lists local and connected
printers with their own driver, so the server renders in the printer
language, and sends each job to winspool as RAW (OpenPrinterW,
StartDocPrinterW, WritePrinter, EndDocPrinter). Other platforms keep
announcing no printers until their backend exists.

Jobs still open when the handle is dropped are aborted in the spooler,
because the addin does not call abort_job when the session ends.
- Write the whole chunk to the spooler: WritePrinter may report fewer
  bytes than requested.
- Abort the job when a write fails. The addin ignores errors on close,
  so the job would otherwise be printed with missing data.
- Give each job its own lock, so a slow spooler call on one printer does
  not stall the worker threads of the other printers.
- Skip queues that cannot be opened when announcing printers, and mark
  the first one as default when Windows has none, as FreeRDP does.
A failed write released the job lock before aborting, so a close arriving
in between could still finish the job and print it incomplete. The failed
job is now taken out while the lock is held.

The chunk loop and the choice of default printer move out of the Windows
backend into plain functions, so they are tested on every platform. The
cross-printer test fails after a timeout instead of hanging when one
printer blocks the others.
When the job counter wraps around, skip ids that an open job still uses,
and remove a map entry only if it still holds the same job, so a late
cleanup cannot drop a newer job that reused the id. The job id is now
reserved before the spooler job starts.

Filtering out printers that cannot be opened is a plain function with its
own test, and the race test waits for the close to reach the job instead
of sleeping.
The device name in the RDPDR device list has 8 bytes. Windows port names
such as "PORTPROMPT:", "HPSmartPrintingPort" or "WSD-..." were cut there,
and the server accepted the devices but never created the printers in the
session. Leave the port empty so the addin announces PRN1, PRN2..., as
FreeRDP does.
Jobs were opened as RAW. With a v4 (XPS) driver such as Microsoft Print to
PDF the spooler's filter pipeline then failed (0x8051100F) and deleted
the job while it was being written. Leave the datatype empty, as FreeRDP
does, so the spooler uses the one the queue expects.
With Microsoft Print to PDF the server already sends a finished PDF, and
the local Print to PDF queue rejects it because it expects XPS: the
spooler's filter pipeline fails and leaves an empty file. Jobs for a
printer with that driver are now written to a temporary file and, when
the job ends, a Save As dialog asks where to keep the PDF. The dialog
runs on its own thread so the printer worker is not blocked. Cancelling
it discards the file.
- Remove the temporary PDF on every path: when flushing it fails, when
  the dialog thread cannot start, and after a failed or cancelled save.
- Call CoUninitialize only after a successful CoInitializeEx.
- Remember each announced printer's driver instead of enumerating all
  printers again for every job.
- Give the dialog the foreground window as owner so it does not open
  behind the session window.
Without a Linux backend the printer integration reported itself as not
available, so no printer was announced to the session.

CUPS queues are listed with libcups, loaded at run time, and announced
with the MS Publisher Imagesetter PostScript driver, as FreeRDP does, so
the server renders PostScript and CUPS converts it for the real printer.
Each job is spooled to a private temporary file (mode 0600) and handed to
CUPS with a single cupsPrintFile call when the server closes it, so CUPS
never holds a job still being written, and aborted or failed jobs never
reach it.
@dkmstr
dkmstr merged commit a3f46aa into master Oct 30, 2026
2 checks passed
@aschumann-virtualcable
aschumann-virtualcable deleted the feat/printer-redirection branch November 3, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants