diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..5c64aa5 --- /dev/null +++ b/DEPLOYMENT.md @@ -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) diff --git a/build_windows.ps1 b/build_windows.ps1 index f563c1d..7335d9a 100644 --- a/build_windows.ps1 +++ b/build_windows.ps1 @@ -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 "" diff --git a/label_printer_gui.py b/label_printer_gui.py index 5708c67..c13aec5 100644 --- a/label_printer_gui.py +++ b/label_printer_gui.py @@ -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__': diff --git a/print_label.py b/print_label.py index 06da2ac..4eb8e0e 100755 --- a/print_label.py +++ b/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) diff --git a/setup_sumatra.ps1 b/setup_sumatra.ps1 new file mode 100644 index 0000000..e595529 --- /dev/null +++ b/setup_sumatra.ps1 @@ -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"