Fix icon loading in PyInstaller builds

PyInstaller bundles data files into sys._MEIPASS, not the executable directory.

- Add get_resource_path() helper in main.py and main_window.py
- Use _MEIPASS for bundled resources (icon, web files)
- Keep app_dir pointing to executable directory for user data (macros.json)

This fixes the missing icon in taskbar and system tray on Windows builds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-03 17:12:39 -08:00
parent 9bba673ba7
commit 418407b2b9
2 changed files with 30 additions and 9 deletions

View File

@@ -5,6 +5,15 @@ import sys
import threading import threading
from typing import Optional from typing import Optional
def get_resource_path(relative_path):
"""Get the path to a bundled resource file."""
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, relative_path)
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QTabWidget, QGridLayout, QLabel, QPushButton, QTabWidget, QGridLayout,
@@ -293,11 +302,12 @@ class MainWindow(QMainWindow):
"""Setup system tray icon.""" """Setup system tray icon."""
self.tray_icon = QSystemTrayIcon(self) self.tray_icon = QSystemTrayIcon(self)
# Load icon # Load icon from bundled resources
icon_path = os.path.join(self.app_dir, "Macro Pad.png") icon_path = get_resource_path("Macro Pad.png")
if os.path.exists(icon_path): if os.path.exists(icon_path):
self.tray_icon.setIcon(QIcon(icon_path)) icon = QIcon(icon_path)
self.setWindowIcon(QIcon(icon_path)) self.tray_icon.setIcon(icon)
self.setWindowIcon(icon)
else: else:
self.tray_icon.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_ComputerIcon)) self.tray_icon.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_ComputerIcon))

21
main.py
View File

@@ -7,21 +7,32 @@ import multiprocessing
def get_app_dir(): def get_app_dir():
"""Get the application directory.""" """Get the application directory (where macros.json and macro_images are stored)."""
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
# Running as compiled executable # Running as compiled executable - use executable's directory for user data
return os.path.dirname(sys.executable) return os.path.dirname(sys.executable)
else: else:
# Running as script # Running as script
return os.path.dirname(os.path.abspath(__file__)) return os.path.dirname(os.path.abspath(__file__))
def get_resource_path(relative_path):
"""Get the path to a bundled resource file."""
if getattr(sys, 'frozen', False):
# Running as compiled executable - resources are in _MEIPASS
base_path = sys._MEIPASS
else:
# Running as script - resources are in the script directory
base_path = os.path.dirname(os.path.abspath(__file__))
return os.path.join(base_path, relative_path)
def main(): def main():
"""Main entry point.""" """Main entry point."""
# Required for multiprocessing on Windows # Required for multiprocessing on Windows
multiprocessing.freeze_support() multiprocessing.freeze_support()
# Get app directory # Get directories
app_dir = get_app_dir() app_dir = get_app_dir()
# Import PySide6 after freeze_support # Import PySide6 after freeze_support
@@ -34,8 +45,8 @@ def main():
app.setApplicationName("MacroPad Server") app.setApplicationName("MacroPad Server")
app.setOrganizationName("MacroPad") app.setOrganizationName("MacroPad")
# Set application icon # Set application icon (from bundled resources)
icon_path = os.path.join(app_dir, "Macro Pad.png") icon_path = get_resource_path("Macro Pad.png")
if os.path.exists(icon_path): if os.path.exists(icon_path):
app.setWindowIcon(QIcon(icon_path)) app.setWindowIcon(QIcon(icon_path))