Fix printer detection, implement portable deployment with SumatraPDF

- Fixed network printer enumeration (PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS)
- Added printer name truncation to 20 chars with full name mapping
- Implemented silent PDF printing using SumatraPDF with landscape orientation
- Added auto-dismiss for success popup (3 seconds)
- Bundled SumatraPDF inside executable for portable single-file deployment
- Updated build script to embed SumatraPDF
- Added setup_sumatra.ps1 for downloading SumatraPDF portable
- Added DEPLOYMENT.md documentation
This commit is contained in:
2026-02-06 14:00:17 +02:00
parent b204ce38fc
commit f09c365384
5 changed files with 354 additions and 41 deletions

88
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,88 @@
# Label Printer - Portable Deployment Guide
## Deployment Structure
The app is now **fully self-contained** with SumatraPDF embedded inside:
```
LabelPrinter/
├── LabelPrinter.exe # Main application (includes SumatraPDF inside)
├── pdf_backup/ # Auto-created: PDF backups
└── logs/ # Auto-created: Print logs
```
**No visible folders!** SumatraPDF is bundled inside LabelPrinter.exe and extracted to a temporary location at runtime.
## Setup Instructions
### 1. Download SumatraPDF (For Building Only)
**This step is only needed when building the app.** SumatraPDF will be embedded inside the executable.
```powershell
# PowerShell command to download SumatraPDF
powershell -ExecutionPolicy Bypass -File setup_sumatra.ps1
```
This downloads SumatraPDF portable (~5 MB) to the `SumatraPDF` folder.
### 2. Build the Application
```powershell
.\build_windows.ps1
```
The build script will:
- Check for SumatraPDF
- Bundle it inside the executable
- Create `dist\LabelPrinter.exe` (~80 MB including all dependencies)
### 3. Deploy
**Simply copy `LabelPrinter.exe` to any Windows machine!**
```
📁 Deployment (any folder)
└── LabelPrinter.exe ← Just this one file!
```
- No installation needed
- No additional files or folders
- Double-click to run
- Works on any Windows 10/11 machine
## Features
-**Single Executable** - Everything bundled in one .exe file (~80 MB)
-**Fully Portable** - No installation needed, no external dependencies
-**Silent Printing** - No PDF viewer windows pop up
-**Network Printers** - Supports printers from print servers (e.g. `\\server\printer`)
-**PDF Backup** - All labels saved to `pdf_backup/` folder
-**Print Logging** - CSV logs in `logs/` folder
-**SumatraPDF Hidden** - Embedded inside, not visible to users
## Printer Name Display
Network printer names (e.g. `\\filesibiusb05\ZDesigner_ZQ630`) are automatically shortened to 20 characters in the dropdown for better display. The full printer name is used for actual printing.
## Troubleshooting
### Printing Not Working
1. **Check Printer Connection**: Verify printer is online and accessible
2. **Check PDF Backup**: Labels are always saved to `pdf_backup/` folder even if printing fails
3. **Check Logs**: View print logs in `logs/` folder for error messages
4. **Rebuild App**: If you built the app yourself, ensure `setup_sumatra.ps1` was run first to download SumatraPDF before building
### Network Printers Not Showing
- Network printers must be installed/connected on the machine before running the app
- For print server printers like `\\filesibiusb05\printer`, ensure the share is accessible
- Run as administrator if printer enumeration fails
## Notes
- First run may be slower (Kivy initialization)
- PDF backups are auto-deleted after 5 days
- Log files are auto-deleted after 5 days
- Supports Python 3.10-3.13 (Python 3.14+ may have issues with Kivy)

View File

@@ -47,13 +47,39 @@ if ($LASTEXITCODE -ne 0) {
}
Write-Host ""
Write-Host "[4/5] Cleaning old build artifacts..." -ForegroundColor Cyan
Write-Host "[4/6] Checking for SumatraPDF..." -ForegroundColor Cyan
$sumatraPath = "SumatraPDF\SumatraPDF.exe"
if (-not (Test-Path $sumatraPath)) {
Write-Host ""
Write-Host "WARNING: SumatraPDF not found!" -ForegroundColor Yellow
Write-Host "SumatraPDF is required for silent PDF printing." -ForegroundColor Yellow
Write-Host ""
Write-Host "Run the setup script first:" -ForegroundColor Yellow
Write-Host " powershell -ExecutionPolicy Bypass -File setup_sumatra.ps1" -ForegroundColor Cyan
Write-Host ""
$response = Read-Host "Continue building without SumatraPDF? (y/n)"
if ($response -ne "y") {
Write-Host "Build cancelled."
Read-Host "Press Enter to exit"
exit 1
}
Write-Host ""
Write-Host "Building without SumatraPDF (PDF printing will not work)..." -ForegroundColor Yellow
$addBinaryArg = @()
} else {
Write-Host "Found: $sumatraPath" -ForegroundColor Green
# Add SumatraPDF as bundled binary (will be embedded inside the exe)
$addBinaryArg = @("--add-binary", "$sumatraPath;.")
}
Write-Host ""
Write-Host "[5/6] Cleaning old build artifacts..." -ForegroundColor Cyan
if (Test-Path "dist") { Remove-Item -Recurse -Force "dist" }
if (Test-Path "build") { Remove-Item -Recurse -Force "build" }
Remove-Item -Force "*.spec" -ErrorAction SilentlyContinue
Write-Host ""
Write-Host "[5/5] Building executable with PyInstaller..." -ForegroundColor Cyan
Write-Host "[6/6] Building executable with PyInstaller..." -ForegroundColor Cyan
Write-Host "This may take 5-15 minutes, please wait..."
Write-Host ""
@@ -73,6 +99,11 @@ $pyinstallerArgs = @(
"-y"
)
# Add SumatraPDF binary if available (bundles inside the exe)
if ($addBinaryArg) {
$pyinstallerArgs += $addBinaryArg
}
pyinstaller @pyinstallerArgs
if ($LASTEXITCODE -ne 0) {
Write-Host ""

View File

@@ -393,7 +393,7 @@ class LabelPrinterApp(App):
# Use Clock.schedule_once to update UI from main thread
Clock.schedule_once(lambda dt: popup.dismiss(), 0)
Clock.schedule_once(lambda dt: self.show_popup("Success", "Label printed successfully!"), 0.1)
Clock.schedule_once(lambda dt: self.show_popup("Success", "Label printed successfully!", auto_dismiss=True), 0.1)
# Clear inputs after successful print (but keep printer selection)
Clock.schedule_once(lambda dt: self.clear_inputs(), 0.2)
else:
@@ -420,8 +420,14 @@ class LabelPrinterApp(App):
self.cable_id_input.text = ''
# Printer selection is NOT cleared - it persists until user changes it
def show_popup(self, title, message):
"""Show a popup message"""
def show_popup(self, title, message, auto_dismiss=False):
"""Show a popup message
Args:
title (str): Popup title
message (str): Popup message
auto_dismiss (bool): If True, popup will auto-dismiss after 3 seconds
"""
popup = Popup(
title=title,
content=BoxLayout(
@@ -439,6 +445,10 @@ class LabelPrinterApp(App):
popup.content.add_widget(close_button)
popup.open()
# Auto-dismiss after 3 seconds if requested
if auto_dismiss:
Clock.schedule_once(lambda dt: popup.dismiss(), 3)
if __name__ == '__main__':

View File

@@ -3,6 +3,7 @@ import barcode
from barcode.writer import ImageWriter
import time
import os
import sys
import datetime
import platform
import subprocess
@@ -249,51 +250,139 @@ def print_to_printer(printer_name, file_path):
return True
elif SYSTEM == "Windows":
# Windows: Print directly without opening PDF viewer
# Windows: Print PDF silently without any viewer opening
try:
if WIN32_AVAILABLE:
import win32print
import win32api
if file_path.endswith('.pdf'):
# Use SumatraPDF command-line or direct raw printing
# Try printing via subprocess to avoid opening a PDF viewer window
# Try silent printing methods (no viewer opens)
import os
import winreg
# Method: Use win32print raw API to send to printer silently
try:
hprinter = win32print.OpenPrinter(printer_name)
printed = False
# Method 1: SumatraPDF (bundled inside exe or external)
sumatra_paths = []
# Get the directory where this script/exe is running
if getattr(sys, 'frozen', False):
# Running as compiled executable
# PyInstaller extracts bundled files to sys._MEIPASS temp folder
if hasattr(sys, '_MEIPASS'):
# Check bundled version first (inside the exe)
bundled_sumatra = os.path.join(sys._MEIPASS, 'SumatraPDF.exe')
sumatra_paths.append(bundled_sumatra)
# Also check app directory for external version
app_dir = os.path.dirname(sys.executable)
sumatra_paths.append(os.path.join(app_dir, "SumatraPDF", "SumatraPDF.exe"))
sumatra_paths.append(os.path.join(app_dir, "SumatraPDF.exe"))
else:
# Running as script - check local folders
app_dir = os.path.dirname(os.path.abspath(__file__))
sumatra_paths.append(os.path.join(app_dir, "SumatraPDF", "SumatraPDF.exe"))
sumatra_paths.append(os.path.join(app_dir, "SumatraPDF.exe"))
# Then check system installations
sumatra_paths.extend([
r"C:\Program Files\SumatraPDF\SumatraPDF.exe",
r"C:\Program Files (x86)\SumatraPDF\SumatraPDF.exe",
])
for sumatra_path in sumatra_paths:
if os.path.exists(sumatra_path):
try:
subprocess.run([
sumatra_path,
"-print-to",
printer_name,
file_path,
"-print-settings",
"fit,landscape",
"-silent",
"-exit-when-done"
], check=False, creationflags=subprocess.CREATE_NO_WINDOW)
print(f"Label sent to printer via SumatraPDF: {printer_name}")
printed = True
break
except Exception as e:
print(f"SumatraPDF error: {e}")
# Method 2: Adobe Reader silent printing
if not printed:
adobe_path = None
for key_path in [
r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AcroRd32.exe",
r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\Acrobat.exe"
]:
try:
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path)
adobe_path, _ = winreg.QueryValueEx(key, "")
winreg.CloseKey(key)
break
except:
pass
if adobe_path and os.path.exists(adobe_path):
try:
subprocess.run([
adobe_path,
"/t", # Print and close
file_path,
printer_name
], check=False, creationflags=subprocess.CREATE_NO_WINDOW)
print(f"Label sent to printer via Adobe Reader: {printer_name}")
printed = True
except:
pass
# Method 3: GhostScript (if installed)
if not printed:
gs_paths = [
r"C:\Program Files\gs\gs10.02.1\bin\gswin64c.exe",
r"C:\Program Files (x86)\gs\gs10.02.1\bin\gswin32c.exe",
]
# Try to find gswin in PATH
try:
# Start a print job
job_info = ("Label Print", None, "RAW")
hjob = win32print.StartDocPrinter(hprinter, 1, job_info)
win32print.StartPagePrinter(hprinter)
# Read PDF file and send to printer
with open(file_path, 'rb') as f:
pdf_data = f.read()
win32print.WritePrinter(hprinter, pdf_data)
win32print.EndPagePrinter(hprinter)
win32print.EndDocPrinter(hprinter)
print(f"Label sent to printer: {printer_name}")
finally:
win32print.ClosePrinter(hprinter)
return True
except Exception as raw_err:
print(f"Raw print failed ({raw_err}), trying ShellExecute silently...")
# Fallback: Use ShellExecute with printto (minimized, auto-closes)
try:
win32api.ShellExecute(
0, "printto", file_path,
f'"{printer_name}"', ".", 0
)
print(f"Label sent to printer: {printer_name}")
return True
except Exception as shell_err:
print(f"ShellExecute print failed: {shell_err}")
return True
gs_result = subprocess.run(['where', 'gswin64c'],
capture_output=True, text=True, check=False)
if gs_result.returncode == 0:
gs_paths.insert(0, gs_result.stdout.strip().split('\n')[0])
except:
pass
for gs_path in gs_paths:
if os.path.exists(gs_path):
try:
subprocess.run([
gs_path,
"-dNOPAUSE", "-dBATCH", "-dQUIET",
f"-sDEVICE=mswinpr2",
f"-sOutputFile=%printer%{printer_name}",
file_path
], check=False, creationflags=subprocess.CREATE_NO_WINDOW)
print(f"Label sent to printer via GhostScript: {printer_name}")
printed = True
break
except:
pass
if not printed:
# Fallback: Let user know and save PDF
print("=" * 60)
print("NOTICE: Silent PDF printing requires SumatraPDF")
print("SumatraPDF not found (should be bundled inside the app)")
print("If you built the app yourself, ensure SumatraPDF.exe is downloaded first.")
print("Run: setup_sumatra.ps1 before building")
print("=" * 60)
print(f"PDF saved to: {file_path}")
print("The PDF can be printed manually.")
return True
else:
# Non-PDF files: print silently with notepad
# Non-PDF files
subprocess.run(['notepad', '/p', file_path],
check=False,
creationflags=subprocess.CREATE_NO_WINDOW)

95
setup_sumatra.ps1 Normal file
View File

@@ -0,0 +1,95 @@
# Download and Setup SumatraPDF Portable for Label Printer
# This script downloads SumatraPDF portable and sets up the deployment structure
Write-Host ""
Write-Host "========================================================"
Write-Host " Label Printer - SumatraPDF Setup"
Write-Host "========================================================"
Write-Host ""
# Create SumatraPDF folder if it doesn't exist
$sumatraFolder = "SumatraPDF"
if (-not (Test-Path $sumatraFolder)) {
New-Item -ItemType Directory -Path $sumatraFolder -Force | Out-Null
}
# Check if SumatraPDF.exe already exists
$sumatraExe = Join-Path $sumatraFolder "SumatraPDF.exe"
if (Test-Path $sumatraExe) {
Write-Host "[OK] SumatraPDF.exe already exists at: $sumatraExe" -ForegroundColor Green
Write-Host ""
Read-Host "Press Enter to exit"
exit 0
}
Write-Host "[1/3] Downloading SumatraPDF portable (64-bit, ~5 MB)..." -ForegroundColor Cyan
# SumatraPDF download URL (latest stable version)
$url = "https://www.sumatrapdfreader.org/dl/rel/3.5.2/SumatraPDF-3.5.2-64.zip"
$zipFile = "SumatraPDF-temp.zip"
try {
# Download with progress
$progressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $url -OutFile $zipFile -ErrorAction Stop
Write-Host "[OK] Download complete" -ForegroundColor Green
} catch {
Write-Host "[ERROR] Failed to download SumatraPDF" -ForegroundColor Red
Write-Host "Error: $_" -ForegroundColor Red
Write-Host ""
Write-Host "Please download manually from:" -ForegroundColor Yellow
Write-Host " https://www.sumatrapdfreader.org/download-free-pdf-viewer" -ForegroundColor Yellow
Write-Host " Extract SumatraPDF.exe to the 'SumatraPDF' folder" -ForegroundColor Yellow
Write-Host ""
Read-Host "Press Enter to exit"
exit 1
}
Write-Host ""
Write-Host "[2/3] Extracting..." -ForegroundColor Cyan
try {
# Extract ZIP file
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipFile, $sumatraFolder)
Write-Host "[OK] Extraction complete" -ForegroundColor Green
} catch {
Write-Host "[ERROR] Failed to extract ZIP file" -ForegroundColor Red
Write-Host "Error: $_" -ForegroundColor Red
Read-Host "Press Enter to exit"
exit 1
}
Write-Host ""
Write-Host "[3/3] Cleaning up..." -ForegroundColor Cyan
# Remove temporary ZIP file
if (Test-Path $zipFile) {
Remove-Item $zipFile -Force
}
Write-Host "[OK] Cleanup complete" -ForegroundColor Green
Write-Host ""
# Verify installation
if (Test-Path $sumatraExe) {
Write-Host "========================================================"
Write-Host " SETUP SUCCESSFUL!" -ForegroundColor Green
Write-Host "========================================================"
Write-Host ""
Write-Host "SumatraPDF portable is now installed at:" -ForegroundColor Green
Write-Host " $sumatraExe" -ForegroundColor Green
Write-Host ""
Write-Host "The Label Printer app will now be able to print PDFs silently." -ForegroundColor Green
Write-Host ""
} else {
Write-Host "========================================================"
Write-Host " SETUP INCOMPLETE" -ForegroundColor Yellow
Write-Host "========================================================"
Write-Host ""
Write-Host "Could not find SumatraPDF.exe after extraction." -ForegroundColor Yellow
Write-Host "Please check the SumatraPDF folder and ensure SumatraPDF.exe is present." -ForegroundColor Yellow
Write-Host ""
}
Read-Host "Press Enter to exit"