main.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import sys
  2. import os
  3. import asyncio
  4. import logging
  5. from pathlib import Path
  6. from PySide6.QtCore import QUrl, QTimer, QObject, QEvent, Slot
  7. from PySide6.QtGui import QGuiApplication
  8. from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType
  9. from qasync import QEventLoop
  10. from backend import Backend
  11. from models.pattern_model import PatternModel
  12. from models.playlist_model import PlaylistModel
  13. from png_cache_manager import ensure_png_cache_startup
  14. # Configure logging
  15. logging.basicConfig(
  16. level=logging.INFO,
  17. format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  18. )
  19. logger = logging.getLogger(__name__)
  20. class ActivityEventFilter(QObject):
  21. """Event filter to track user activity for screen timeout (linuxfb compatible)"""
  22. def __init__(self):
  23. super().__init__()
  24. self.backend = None # Will be set after QML loads
  25. self.activity_events = {
  26. QEvent.MouseButtonPress,
  27. QEvent.MouseButtonRelease,
  28. QEvent.MouseMove,
  29. QEvent.TouchBegin,
  30. QEvent.TouchUpdate,
  31. QEvent.TouchEnd,
  32. QEvent.KeyPress,
  33. QEvent.KeyRelease
  34. }
  35. @Slot(QObject)
  36. def set_backend(self, backend):
  37. """Set the backend instance after QML loads"""
  38. self.backend = backend
  39. logger.info("📡 Backend connected to activity event filter")
  40. def eventFilter(self, obj, event):
  41. """Filter events and reset activity timer on user interaction"""
  42. if self.backend and event.type() in self.activity_events:
  43. # Call backend to reset activity timer
  44. try:
  45. self.backend.resetActivityTimer()
  46. except Exception as e:
  47. logger.error(f"Failed to reset activity timer: {e}")
  48. # Always return False to let event propagate normally
  49. return False
  50. async def startup_tasks():
  51. """Run async startup tasks"""
  52. logger.info("🚀 Starting dune-weaver-touch async initialization...")
  53. # Ensure PNG cache is available for all WebP previews
  54. try:
  55. logger.info("🎨 Checking PNG preview cache...")
  56. png_cache_success = await ensure_png_cache_startup()
  57. if png_cache_success:
  58. logger.info("✅ PNG cache check completed successfully")
  59. else:
  60. logger.warning("⚠️ PNG cache check completed with warnings")
  61. except Exception as e:
  62. logger.error(f"❌ PNG cache check failed: {e}")
  63. logger.info("✨ dune-weaver-touch startup tasks completed")
  64. def main():
  65. # Set Qt platform to linuxfb for Raspberry Pi compatibility
  66. # This must be set before QGuiApplication is created
  67. if 'QT_QPA_PLATFORM' not in os.environ:
  68. os.environ['QT_QPA_PLATFORM'] = 'linuxfb'
  69. os.environ['QT_QPA_FB_DRM'] = '1'
  70. os.environ['QT_QPA_FONTDIR'] = '/usr/share/fonts'
  71. # Enable virtual keyboard
  72. os.environ['QT_IM_MODULE'] = 'qtvirtualkeyboard'
  73. app = QGuiApplication(sys.argv)
  74. # Setup async event loop
  75. loop = QEventLoop(app)
  76. asyncio.set_event_loop(loop)
  77. # Install global event filter for activity tracking (linuxfb compatible)
  78. # Create it early so it's ready when backend is created
  79. event_filter = ActivityEventFilter()
  80. app.installEventFilter(event_filter)
  81. logger.info("📡 Activity event filter installed")
  82. # Register types
  83. qmlRegisterType(Backend, "DuneWeaver", 1, 0, "Backend")
  84. qmlRegisterType(PatternModel, "DuneWeaver", 1, 0, "PatternModel")
  85. qmlRegisterType(PlaylistModel, "DuneWeaver", 1, 0, "PlaylistModel")
  86. # Load QML
  87. engine = QQmlApplicationEngine()
  88. # Store event filter reference so QML can access it
  89. engine.rootContext().setContextProperty("activityFilter", event_filter)
  90. qml_file = Path(__file__).parent / "qml" / "main.qml"
  91. engine.load(QUrl.fromLocalFile(str(qml_file)))
  92. if not engine.rootObjects():
  93. return -1
  94. # Schedule startup tasks after a brief delay to ensure event loop is running
  95. def schedule_startup():
  96. try:
  97. # Check if we're in an event loop context
  98. current_loop = asyncio.get_running_loop()
  99. current_loop.create_task(startup_tasks())
  100. except RuntimeError:
  101. # No running loop, create task directly
  102. asyncio.create_task(startup_tasks())
  103. # Use QTimer to delay startup tasks
  104. startup_timer = QTimer()
  105. startup_timer.timeout.connect(schedule_startup)
  106. startup_timer.setSingleShot(True)
  107. startup_timer.start(100) # 100ms delay
  108. with loop:
  109. loop.run_forever()
  110. return 0
  111. if __name__ == "__main__":
  112. sys.exit(main())