main.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import sys
  2. import os
  3. import asyncio
  4. import logging
  5. import time
  6. from pathlib import Path
  7. from PySide6.QtCore import QUrl, QTimer, QObject, QEvent
  8. from PySide6.QtGui import QGuiApplication, QTouchEvent, QMouseEvent
  9. from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType
  10. from qasync import QEventLoop
  11. from backend import Backend
  12. from models.pattern_model import PatternModel
  13. from models.playlist_model import PlaylistModel
  14. from png_cache_manager import ensure_png_cache_startup
  15. # Configure logging
  16. logging.basicConfig(
  17. level=logging.INFO,
  18. format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  19. )
  20. logger = logging.getLogger(__name__)
  21. class FirstTouchFilter(QObject):
  22. """
  23. Event filter that ignores the first touch event after inactivity.
  24. Many capacitive touchscreens need the first touch to wake up or calibrate,
  25. and this touch often has incorrect coordinates.
  26. """
  27. def __init__(self, idle_threshold_seconds=2.0):
  28. super().__init__()
  29. self.idle_threshold = idle_threshold_seconds
  30. self.last_touch_time = 0
  31. self.ignore_next_touch = False
  32. logger.info(f"👆 First-touch filter initialized (idle threshold: {idle_threshold_seconds}s)")
  33. def eventFilter(self, obj, event):
  34. """Filter out the first touch after idle period"""
  35. event_type = event.type()
  36. # Handle touch events
  37. if event_type == QEvent.Type.TouchBegin:
  38. current_time = time.time()
  39. time_since_last_touch = current_time - self.last_touch_time
  40. # If it's been more than threshold since last touch, ignore this one
  41. if time_since_last_touch > self.idle_threshold:
  42. logger.debug(f"👆 Ignoring wake-up touch (idle for {time_since_last_touch:.1f}s)")
  43. self.last_touch_time = current_time
  44. return True # Filter out (ignore) this event
  45. self.last_touch_time = current_time
  46. elif event_type in (QEvent.Type.TouchUpdate, QEvent.Type.TouchEnd):
  47. # Update last touch time for any touch activity
  48. self.last_touch_time = time.time()
  49. # Pass through the event
  50. return False
  51. async def startup_tasks():
  52. """Run async startup tasks"""
  53. logger.info("🚀 Starting dune-weaver-touch async initialization...")
  54. # Ensure PNG cache is available for all WebP previews
  55. try:
  56. logger.info("🎨 Checking PNG preview cache...")
  57. png_cache_success = await ensure_png_cache_startup()
  58. if png_cache_success:
  59. logger.info("✅ PNG cache check completed successfully")
  60. else:
  61. logger.warning("⚠️ PNG cache check completed with warnings")
  62. except Exception as e:
  63. logger.error(f"❌ PNG cache check failed: {e}")
  64. logger.info("✨ dune-weaver-touch startup tasks completed")
  65. def main():
  66. # Enable virtual keyboard
  67. os.environ['QT_IM_MODULE'] = 'qtvirtualkeyboard'
  68. app = QGuiApplication(sys.argv)
  69. # Install first-touch filter to ignore wake-up touches
  70. # Ignores the first touch after 2 seconds of inactivity
  71. first_touch_filter = FirstTouchFilter(idle_threshold_seconds=2.0)
  72. app.installEventFilter(first_touch_filter)
  73. logger.info("✅ First-touch filter installed on application")
  74. # Setup async event loop
  75. loop = QEventLoop(app)
  76. asyncio.set_event_loop(loop)
  77. # Register types
  78. qmlRegisterType(Backend, "DuneWeaver", 1, 0, "Backend")
  79. qmlRegisterType(PatternModel, "DuneWeaver", 1, 0, "PatternModel")
  80. qmlRegisterType(PlaylistModel, "DuneWeaver", 1, 0, "PlaylistModel")
  81. # Load QML
  82. engine = QQmlApplicationEngine()
  83. qml_file = Path(__file__).parent / "qml" / "main.qml"
  84. engine.load(QUrl.fromLocalFile(str(qml_file)))
  85. if not engine.rootObjects():
  86. return -1
  87. # Schedule startup tasks after a brief delay to ensure event loop is running
  88. def schedule_startup():
  89. try:
  90. # Check if we're in an event loop context
  91. current_loop = asyncio.get_running_loop()
  92. current_loop.create_task(startup_tasks())
  93. except RuntimeError:
  94. # No running loop, create task directly
  95. asyncio.create_task(startup_tasks())
  96. # Use QTimer to delay startup tasks
  97. startup_timer = QTimer()
  98. startup_timer.timeout.connect(schedule_startup)
  99. startup_timer.setSingleShot(True)
  100. startup_timer.start(100) # 100ms delay
  101. with loop:
  102. loop.run_forever()
  103. return 0
  104. if __name__ == "__main__":
  105. sys.exit(main())