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:
88
DEPLOYMENT.md
Normal file
88
DEPLOYMENT.md
Normal 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)
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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__':
|
||||
|
||||
161
print_label.py
161
print_label.py
@@ -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
95
setup_sumatra.ps1
Normal 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"
|
||||
Reference in New Issue
Block a user