| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- import argparse
- import asyncio
- import signal
- import sys
- from src.application import Application
- from src.utils.logging_config import get_logger, setup_logging
- logger = get_logger(__name__)
- def parse_args():
- """
- 解析命令行参数.
- """
- parser = argparse.ArgumentParser(description="小智Ai客户端")
- parser.add_argument(
- "--mode",
- choices=["gui", "cli"],
- default="gui",
- help="运行模式:gui(图形界面) 或 cli(命令行)",
- )
- parser.add_argument(
- "--protocol",
- choices=["mqtt", "websocket"],
- default="websocket",
- help="通信协议:mqtt 或 websocket",
- )
- parser.add_argument(
- "--skip-activation",
- action="store_true",
- help="跳过激活流程,直接启动应用(仅用于调试)",
- )
- return parser.parse_args()
- async def handle_activation(mode: str) -> bool:
- """处理设备激活流程,依赖已有事件循环.
- Args:
- mode: 运行模式,"gui"或"cli"
- Returns:
- bool: 激活是否成功
- """
- try:
- from src.core.system_initializer import SystemInitializer
- logger.info("开始设备激活流程检查...")
- system_initializer = SystemInitializer()
- # 统一使用 SystemInitializer 内的激活处理,GUI/CLI 自适应
- result = await system_initializer.handle_activation_process(mode=mode)
- success = bool(result.get("is_activated", False))
- logger.info(f"激活流程完成,结果: {success}")
- return success
- except Exception as e:
- logger.error(f"激活流程异常: {e}", exc_info=True)
- return False
- async def start_app(mode: str, protocol: str, skip_activation: bool) -> int:
- """
- 启动应用的统一入口(在已有事件循环中执行).
- """
- logger.info("启动小智AI客户端")
- # 处理激活流程
- if not skip_activation:
- activation_success = await handle_activation(mode)
- if not activation_success:
- logger.error("设备激活失败,程序退出")
- return 1
- else:
- logger.warning("跳过激活流程(调试模式)")
- # 创建并启动应用程序
- app = Application.get_instance()
- return await app.run(mode=mode, protocol=protocol)
- if __name__ == "__main__":
- exit_code = 1
- try:
- args = parse_args()
- setup_logging()
- # 检测Wayland环境并设置Qt平台插件配置
- import os
- is_wayland = (
- os.environ.get("WAYLAND_DISPLAY")
- or os.environ.get("XDG_SESSION_TYPE") == "wayland"
- )
- if args.mode == "gui" and is_wayland:
- # 在Wayland环境下,确保Qt使用正确的平台插件
- if "QT_QPA_PLATFORM" not in os.environ:
- # 优先使用wayland插件,失败则回退到xcb(X11兼容层)
- os.environ["QT_QPA_PLATFORM"] = "wayland;xcb"
- logger.info("Wayland环境:设置QT_QPA_PLATFORM=wayland;xcb")
- # 禁用一些在Wayland下不稳定的Qt特性
- os.environ.setdefault("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1")
- logger.info("Wayland环境检测完成,已应用兼容性配置")
- # 统一设置信号处理:忽略 macOS 上可能出现的 SIGTRAP,避免“trace trap”导致进程退出
- try:
- if hasattr(signal, "SIGINT"):
- # 交由 qasync/Qt 处理 Ctrl+C;保持默认或后续由 GUI 层处理
- pass
- if hasattr(signal, "SIGTERM"):
- # 允许进程收到终止信号时走正常关闭路径
- pass
- if hasattr(signal, "SIGTRAP"):
- signal.signal(signal.SIGTRAP, signal.SIG_IGN)
- except Exception:
- # 某些平台/环境不支持设置这些信号,忽略即可
- pass
- if args.mode == "gui":
- # 在GUI模式下,由main统一创建 QApplication 与 qasync 事件循环
- try:
- import qasync
- from PyQt5.QtWidgets import QApplication
- except ImportError as e:
- logger.error(f"GUI模式需要qasync和PyQt5库: {e}")
- sys.exit(1)
- qt_app = QApplication.instance() or QApplication(sys.argv)
- loop = qasync.QEventLoop(qt_app)
- asyncio.set_event_loop(loop)
- logger.info("已在main中创建qasync事件循环")
- # 确保关闭最后一个窗口不会自动退出应用,避免事件环提前停止
- try:
- qt_app.setQuitOnLastWindowClosed(False)
- except Exception:
- pass
- with loop:
- exit_code = loop.run_until_complete(
- start_app(args.mode, args.protocol, args.skip_activation)
- )
- else:
- # CLI模式使用标准asyncio事件循环
- exit_code = asyncio.run(
- start_app(args.mode, args.protocol, args.skip_activation)
- )
- except KeyboardInterrupt:
- logger.info("程序被用户中断")
- exit_code = 0
- except Exception as e:
- logger.error(f"程序异常退出: {e}", exc_info=True)
- exit_code = 1
- finally:
- sys.exit(exit_code)
|