Skip to content

Initial PrintBridge implementation: Windows tray app for web-to-printer - #1

Merged
andrewsauder merged 3 commits into
mainfrom
claude/printbridge-app-ijc1n4-iiqpgq
Aug 14, 2026
Merged

Initial PrintBridge implementation: Windows tray app for web-to-printer#1
andrewsauder merged 3 commits into
mainfrom
claude/printbridge-app-ijc1n4-iiqpgq

Conversation

@andrewsauder

Copy link
Copy Markdown
Member

This is the initial implementation of PrintBridge, a Windows tray application that enables web applications to print to desktop printers via a local HTTP API.

Summary

PrintBridge provides a bridge between web browsers and Windows printers. Web applications POST PDF documents to a local HTTP API (default port 7227), and PrintBridge silently renders and spools them to configured printers without user interaction. Administrators configure named print queues (mapping queue names to Windows printers) and allowlist website origins that can access the API.

Key Changes

  • Core printing pipeline (PrintService.cs): Renders PDF pages at printer resolution using PDFium (via PDFtoImage) and hands them to the Windows spooler with StandardPrintController for silent printing
  • Print job management (PrintJobManager.cs): In-memory registry that queues jobs FIFO, executes them serially to bound memory usage, and retains terminal jobs for browser observation
  • HTTP API (ApiEndpoints.cs, WebHostRunner.cs): ASP.NET Core Kestrel listener bound to 127.0.0.1 with CORS enforcement, supporting /api/v1/status, /api/v1/queues, and /api/v1/print-jobs endpoints
  • Settings management (SettingsStore.cs, AppSettings.cs): JSON-based configuration for queues, port, allowed origins, and run-at-login preference with atomic writes
  • UI (SettingsForm.cs, QueueEditDialog.cs, TrayApplicationContext.cs): WinForms-based settings window and tray icon menu, built in code rather than designer for reviewability
  • Comprehensive test suite: Tests for queue resolution, settings persistence, page fitting math, PDF sniffing, job management, and origin allowlisting
  • Build and release infrastructure: GitHub Actions CI/CD, Inno Setup installer script, and portable build support

Notable Implementation Details

  • Named queues abstraction: Web apps never see Windows printer names; they request queues by name, allowing printer swaps without code changes
  • Silent printing: Uses StandardPrintController instead of the default controller to suppress print dialogs
  • Per-page orientation: Automatically detects landscape vs. portrait from PDF page dimensions
  • Atomic settings writes: Uses temp file + replace pattern to prevent corruption on crash
  • Single-instance enforcement: Mutex prevents multiple instances from running and competing for the port
  • CORS enforcement: Browser requests must come from allowlisted origins; same-machine tools (curl, desktop apps) bypass CORS
  • Structured error codes: Machine-readable error codes (unknownQueue, invalidDocument, etc.) for API clients

https://claude.ai/code/session_013Ym9ifwDcWhR56pTn6BZzd

claude added 3 commits August 14, 2026 12:31
PrintBridge is the printing sibling of ScanBridge: a Windows tray app with a
loopback-only HTTP API that web pages POST a PDF to, which is then rendered and
handed to the Windows print spooler with no dialog and no user interaction.

Mirrors ScanBridge's structure — settings store, Kestrel host, tray context,
code-built settings window, job manager with polling — with the differences the
print direction implies:

- Named queues. Administrators map a queue name to a Windows printer; callers
  only ever use the name, so the printer behind a queue can be swapped without
  touching a web app and no caller learns the machine's printer names.
- FIFO drain loop instead of a busy rejection. Submissions are always accepted
  and printed one at a time, which bounds the memory cost of rendering pages at
  printer resolution.
- PDFtoImage (PDFium + SkiaSharp, MIT/BSD-3) rasterizes each page lazily at the
  printer's own resolution; System.Drawing.Printing spools it, with
  StandardPrintController for silent printing, per-page orientation from the PDF
  and scale-to-fit-never-enlarge centering.

The API is documented in docs/api.md: status, queues, submit (raw PDF body plus
?queue=&copies=), poll, cancel. Tests cover queue resolution, the PDF sniffer,
job manager behaviour, page fitting math and the settings round-trip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Ym9ifwDcWhR56pTn6BZzd
Two fixes after the first CI run (which built clean and failed one test):

- Page placement measured PageSettings.PrintableArea and swapped it by hand for
  landscape pages. The .NET implementation already swaps that value itself, and
  which way it lands depends on the driver, so the workaround was a coin flip.
  Graphics.VisibleClipBounds comes straight from the printer device context and
  is therefore in the page's real orientation whatever the driver does; PageBounds
  (verifiably orientation-aware) stays as the fallback. FitCentered now takes the
  area as a rectangle so a non-zero clip origin is honored.
- JobsAreDrainedInSubmissionOrder waited for the job status to turn "printing",
  which the manager sets just before it calls the executor — so the assertion
  could run before the executor recorded anything. It now waits on the executor's
  own record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Ym9ifwDcWhR56pTn6BZzd
The queue editor already checks the installed-printer list itself when it has to
show a queue whose printer is gone, so nothing calls this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Ym9ifwDcWhR56pTn6BZzd
@andrewsauder
andrewsauder merged commit 9a69d6c into main Aug 14, 2026
2 checks passed
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