- Implement SingleInstance class using QSharedMemory and file locking - Cross-platform support with fcntl on Unix/macOS and file creation on Windows - Show warning dialog when user tries to launch second instance - Automatic cleanup of resources on application exit - Graceful handling of instance detection failures 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
165 lines
4.9 KiB
Python
165 lines
4.9 KiB
Python
"""
|
|
Main application entry point for the Cluster4NPU UI application.
|
|
|
|
This module initializes the PyQt5 application, applies the theme, and launches
|
|
the main dashboard window. It serves as the primary entry point for the
|
|
modularized UI application.
|
|
|
|
Main Components:
|
|
- Application initialization and configuration
|
|
- Theme application and font setup
|
|
- Main window instantiation and display
|
|
- Application event loop management
|
|
|
|
Usage:
|
|
python -m cluster4npu_ui.main
|
|
|
|
# Or directly:
|
|
from main import main
|
|
main()
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import tempfile
|
|
from PyQt5.QtWidgets import QApplication, QMessageBox
|
|
from PyQt5.QtGui import QFont
|
|
from PyQt5.QtCore import Qt, QSharedMemory
|
|
|
|
# Import fcntl only on Unix-like systems
|
|
try:
|
|
import fcntl
|
|
HAS_FCNTL = True
|
|
except ImportError:
|
|
HAS_FCNTL = False
|
|
|
|
# Add the parent directory to the path for imports
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from config.theme import apply_theme
|
|
from ui.windows.login import DashboardLogin
|
|
|
|
|
|
class SingleInstance:
|
|
"""Ensure only one instance of the application can run."""
|
|
|
|
def __init__(self, app_name="Cluster4NPU"):
|
|
self.app_name = app_name
|
|
self.shared_memory = QSharedMemory(app_name)
|
|
self.lock_file = None
|
|
self.lock_fd = None
|
|
|
|
def is_running(self):
|
|
"""Check if another instance is already running."""
|
|
# Try to create shared memory
|
|
if self.shared_memory.attach():
|
|
# Another instance is already running
|
|
return True
|
|
|
|
# Try to create the shared memory
|
|
if not self.shared_memory.create(1):
|
|
# Failed to create, likely another instance exists
|
|
return True
|
|
|
|
# Also use file locking as backup (works better on some systems)
|
|
if HAS_FCNTL:
|
|
try:
|
|
self.lock_file = os.path.join(tempfile.gettempdir(), f"{self.app_name}.lock")
|
|
self.lock_fd = os.open(self.lock_file, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
|
fcntl.lockf(self.lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
except (OSError, IOError):
|
|
# Another instance is running
|
|
if self.lock_fd:
|
|
os.close(self.lock_fd)
|
|
return True
|
|
else:
|
|
# On Windows, try simple file creation
|
|
try:
|
|
self.lock_file = os.path.join(tempfile.gettempdir(), f"{self.app_name}.lock")
|
|
self.lock_fd = os.open(self.lock_file, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
|
except (OSError, IOError):
|
|
return True
|
|
|
|
return False
|
|
|
|
def cleanup(self):
|
|
"""Clean up resources."""
|
|
if self.shared_memory.isAttached():
|
|
self.shared_memory.detach()
|
|
|
|
if self.lock_fd:
|
|
try:
|
|
if HAS_FCNTL:
|
|
fcntl.lockf(self.lock_fd, fcntl.LOCK_UN)
|
|
os.close(self.lock_fd)
|
|
os.unlink(self.lock_file)
|
|
except:
|
|
pass
|
|
|
|
|
|
def setup_application():
|
|
"""Initialize and configure the QApplication."""
|
|
# Enable high DPI support BEFORE creating QApplication
|
|
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
|
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
|
|
|
# Create QApplication if it doesn't exist
|
|
if not QApplication.instance():
|
|
app = QApplication(sys.argv)
|
|
else:
|
|
app = QApplication.instance()
|
|
|
|
# Set application properties
|
|
app.setApplicationName("Cluster4NPU")
|
|
app.setApplicationVersion("1.0.0")
|
|
app.setOrganizationName("Cluster4NPU Team")
|
|
|
|
# Set application font
|
|
app.setFont(QFont("Arial", 9))
|
|
|
|
# Apply the harmonious theme
|
|
apply_theme(app)
|
|
|
|
return app
|
|
|
|
|
|
def main():
|
|
"""Main application entry point."""
|
|
# Create a minimal QApplication first for the message box
|
|
temp_app = QApplication(sys.argv) if not QApplication.instance() else QApplication.instance()
|
|
|
|
# Check for single instance
|
|
single_instance = SingleInstance()
|
|
|
|
if single_instance.is_running():
|
|
QMessageBox.warning(
|
|
None,
|
|
"Application Already Running",
|
|
"Cluster4NPU is already running. Please check your taskbar or system tray.",
|
|
)
|
|
sys.exit(0)
|
|
|
|
try:
|
|
# Setup the full application
|
|
app = setup_application()
|
|
|
|
# Create and show the main dashboard login window
|
|
dashboard = DashboardLogin()
|
|
dashboard.show()
|
|
|
|
# Clean up single instance on app exit
|
|
app.aboutToQuit.connect(single_instance.cleanup)
|
|
|
|
# Start the application event loop
|
|
sys.exit(app.exec_())
|
|
|
|
except Exception as e:
|
|
print(f"Error starting application: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
single_instance.cleanup()
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main() |