Compare commits

..

2 Commits

Author SHA1 Message Date
58082ed171 final doc 2026-02-13 15:30:00 +02:00
f09c365384 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
2026-02-06 14:00:17 +02:00
10 changed files with 355 additions and 42 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

@@ -4,7 +4,7 @@
a = Analysis( a = Analysis(
['label_printer_gui.py'], ['label_printer_gui.py'],
pathex=[], pathex=[],
binaries=[], binaries=[('SumatraPDF\\SumatraPDF.exe', '.')],
datas=[], datas=[],
hiddenimports=['kivy', 'PIL', 'barcode', 'reportlab', 'print_label', 'print_label_pdf'], hiddenimports=['kivy', 'PIL', 'barcode', 'reportlab', 'print_label', 'print_label_pdf'],
hookspath=[], hookspath=[],

BIN
SumatraPDF/SumatraPDF.exe Normal file

Binary file not shown.

View File

@@ -47,13 +47,39 @@ if ($LASTEXITCODE -ne 0) {
} }
Write-Host "" 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 "dist") { Remove-Item -Recurse -Force "dist" }
if (Test-Path "build") { Remove-Item -Recurse -Force "build" } if (Test-Path "build") { Remove-Item -Recurse -Force "build" }
Remove-Item -Force "*.spec" -ErrorAction SilentlyContinue Remove-Item -Force "*.spec" -ErrorAction SilentlyContinue
Write-Host "" 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 "This may take 5-15 minutes, please wait..."
Write-Host "" Write-Host ""
@@ -73,6 +99,11 @@ $pyinstallerArgs = @(
"-y" "-y"
) )
# Add SumatraPDF binary if available (bundles inside the exe)
if ($addBinaryArg) {
$pyinstallerArgs += $addBinaryArg
}
pyinstaller @pyinstallerArgs pyinstaller @pyinstallerArgs
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Host "" Write-Host ""

BIN
dist/LabelPrinter.exe vendored Normal file

Binary file not shown.

View File

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

View File

@@ -3,6 +3,7 @@ import barcode
from barcode.writer import ImageWriter from barcode.writer import ImageWriter
import time import time
import os import os
import sys
import datetime import datetime
import platform import platform
import subprocess import subprocess
@@ -249,51 +250,139 @@ def print_to_printer(printer_name, file_path):
return True return True
elif SYSTEM == "Windows": elif SYSTEM == "Windows":
# Windows: Print directly without opening PDF viewer # Windows: Print PDF silently without any viewer opening
try: try:
if WIN32_AVAILABLE: if WIN32_AVAILABLE:
import win32print import win32print
import win32api import win32api
if file_path.endswith('.pdf'): if file_path.endswith('.pdf'):
# Use SumatraPDF command-line or direct raw printing # Try silent printing methods (no viewer opens)
# Try printing via subprocess to avoid opening a PDF viewer window import os
import winreg
# Method: Use win32print raw API to send to printer silently printed = False
try:
hprinter = win32print.OpenPrinter(printer_name) # 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: try:
# Start a print job gs_result = subprocess.run(['where', 'gswin64c'],
job_info = ("Label Print", None, "RAW") capture_output=True, text=True, check=False)
hjob = win32print.StartDocPrinter(hprinter, 1, job_info) if gs_result.returncode == 0:
win32print.StartPagePrinter(hprinter) gs_paths.insert(0, gs_result.stdout.strip().split('\n')[0])
except:
# Read PDF file and send to printer pass
with open(file_path, 'rb') as f:
pdf_data = f.read() for gs_path in gs_paths:
win32print.WritePrinter(hprinter, pdf_data) if os.path.exists(gs_path):
try:
win32print.EndPagePrinter(hprinter) subprocess.run([
win32print.EndDocPrinter(hprinter) gs_path,
print(f"Label sent to printer: {printer_name}") "-dNOPAUSE", "-dBATCH", "-dQUIET",
finally: f"-sDEVICE=mswinpr2",
win32print.ClosePrinter(hprinter) f"-sOutputFile=%printer%{printer_name}",
return True file_path
except Exception as raw_err: ], check=False, creationflags=subprocess.CREATE_NO_WINDOW)
print(f"Raw print failed ({raw_err}), trying ShellExecute silently...") print(f"Label sent to printer via GhostScript: {printer_name}")
# Fallback: Use ShellExecute with printto (minimized, auto-closes) printed = True
try: break
win32api.ShellExecute( except:
0, "printto", file_path, pass
f'"{printer_name}"', ".", 0
) if not printed:
print(f"Label sent to printer: {printer_name}") # Fallback: Let user know and save PDF
return True print("=" * 60)
except Exception as shell_err: print("NOTICE: Silent PDF printing requires SumatraPDF")
print(f"ShellExecute print failed: {shell_err}") print("SumatraPDF not found (should be bundled inside the app)")
return True 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: else:
# Non-PDF files: print silently with notepad # Non-PDF files
subprocess.run(['notepad', '/p', file_path], subprocess.run(['notepad', '/p', file_path],
check=False, check=False,
creationflags=subprocess.CREATE_NO_WINDOW) 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"