main.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import argparse
  2. import asyncio
  3. import signal
  4. import sys
  5. from src.application import Application
  6. from src.utils.logging_config import get_logger, setup_logging
  7. logger = get_logger(__name__)
  8. def parse_args():
  9. """
  10. 解析命令行参数.
  11. """
  12. parser = argparse.ArgumentParser(description="小智Ai客户端")
  13. parser.add_argument(
  14. "--mode",
  15. choices=["gui", "cli"],
  16. default="gui",
  17. help="运行模式:gui(图形界面) 或 cli(命令行)",
  18. )
  19. parser.add_argument(
  20. "--protocol",
  21. choices=["mqtt", "websocket"],
  22. default="websocket",
  23. help="通信协议:mqtt 或 websocket",
  24. )
  25. parser.add_argument(
  26. "--skip-activation",
  27. action="store_true",
  28. help="跳过激活流程,直接启动应用(仅用于调试)",
  29. )
  30. return parser.parse_args()
  31. async def handle_activation(mode: str) -> bool:
  32. """处理设备激活流程,依赖已有事件循环.
  33. Args:
  34. mode: 运行模式,"gui"或"cli"
  35. Returns:
  36. bool: 激活是否成功
  37. """
  38. try:
  39. from src.core.system_initializer import SystemInitializer
  40. logger.info("开始设备激活流程检查...")
  41. system_initializer = SystemInitializer()
  42. # 统一使用 SystemInitializer 内的激活处理,GUI/CLI 自适应
  43. result = await system_initializer.handle_activation_process(mode=mode)
  44. success = bool(result.get("is_activated", False))
  45. logger.info(f"激活流程完成,结果: {success}")
  46. return success
  47. except Exception as e:
  48. logger.error(f"激活流程异常: {e}", exc_info=True)
  49. return False
  50. async def start_app(mode: str, protocol: str, skip_activation: bool) -> int:
  51. """
  52. 启动应用的统一入口(在已有事件循环中执行).
  53. """
  54. logger.info("启动小智AI客户端")
  55. # 处理激活流程
  56. if not skip_activation:
  57. activation_success = await handle_activation(mode)
  58. if not activation_success:
  59. logger.error("设备激活失败,程序退出")
  60. return 1
  61. else:
  62. logger.warning("跳过激活流程(调试模式)")
  63. # 创建并启动应用程序
  64. app = Application.get_instance()
  65. return await app.run(mode=mode, protocol=protocol)
  66. if __name__ == "__main__":
  67. exit_code = 1
  68. try:
  69. args = parse_args()
  70. setup_logging()
  71. # 检测Wayland环境并设置Qt平台插件配置
  72. import os
  73. is_wayland = (
  74. os.environ.get("WAYLAND_DISPLAY")
  75. or os.environ.get("XDG_SESSION_TYPE") == "wayland"
  76. )
  77. if args.mode == "gui" and is_wayland:
  78. # 在Wayland环境下,确保Qt使用正确的平台插件
  79. if "QT_QPA_PLATFORM" not in os.environ:
  80. # 优先使用wayland插件,失败则回退到xcb(X11兼容层)
  81. os.environ["QT_QPA_PLATFORM"] = "wayland;xcb"
  82. logger.info("Wayland环境:设置QT_QPA_PLATFORM=wayland;xcb")
  83. # 禁用一些在Wayland下不稳定的Qt特性
  84. os.environ.setdefault("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1")
  85. logger.info("Wayland环境检测完成,已应用兼容性配置")
  86. # 统一设置信号处理:忽略 macOS 上可能出现的 SIGTRAP,避免“trace trap”导致进程退出
  87. try:
  88. if hasattr(signal, "SIGINT"):
  89. # 交由 qasync/Qt 处理 Ctrl+C;保持默认或后续由 GUI 层处理
  90. pass
  91. if hasattr(signal, "SIGTERM"):
  92. # 允许进程收到终止信号时走正常关闭路径
  93. pass
  94. if hasattr(signal, "SIGTRAP"):
  95. signal.signal(signal.SIGTRAP, signal.SIG_IGN)
  96. except Exception:
  97. # 某些平台/环境不支持设置这些信号,忽略即可
  98. pass
  99. if args.mode == "gui":
  100. # 在GUI模式下,由main统一创建 QApplication 与 qasync 事件循环
  101. try:
  102. import qasync
  103. from PyQt5.QtWidgets import QApplication
  104. except ImportError as e:
  105. logger.error(f"GUI模式需要qasync和PyQt5库: {e}")
  106. sys.exit(1)
  107. qt_app = QApplication.instance() or QApplication(sys.argv)
  108. loop = qasync.QEventLoop(qt_app)
  109. asyncio.set_event_loop(loop)
  110. logger.info("已在main中创建qasync事件循环")
  111. # 确保关闭最后一个窗口不会自动退出应用,避免事件环提前停止
  112. try:
  113. qt_app.setQuitOnLastWindowClosed(False)
  114. except Exception:
  115. pass
  116. with loop:
  117. exit_code = loop.run_until_complete(
  118. start_app(args.mode, args.protocol, args.skip_activation)
  119. )
  120. else:
  121. # CLI模式使用标准asyncio事件循环
  122. exit_code = asyncio.run(
  123. start_app(args.mode, args.protocol, args.skip_activation)
  124. )
  125. except KeyboardInterrupt:
  126. logger.info("程序被用户中断")
  127. exit_code = 0
  128. except Exception as e:
  129. logger.error(f"程序异常退出: {e}", exc_info=True)
  130. exit_code = 1
  131. finally:
  132. sys.exit(exit_code)