# Linux Qt 桌面宠物体修复记录 ## 问题描述 Fedora 43 上运行 `python3 main.py` 时: 1. **Ctrl-C 无法停止** — 程序忽略 SIGINT,窗口也看不见 2. **无任何窗口出现** — `python3 main.py` 没有任何输出就崩溃或无响应 3. **错误信息模糊** — 无明确报错,难以定位根因 ## 修复过程(4 轮迭代) --- ### 第 1 轮:包名错误 + Ctrl-C 无法停止 **问题 1:包名错误** ```bash sudo dnf install qt5-qtbase-xcb # Error: No match for argument: qt5-qtbase-xcb ``` Fedora 43 默认使用 **Qt6**,而代码安装的是 PySide6 (Qt6)。包名应为 `qt6-qtbase-xcb`,不是 `qt5`。 **问题 2:Ctrl-C 无法停止** `app.exec()` 阻塞主线程,Python 默认不处理 `SIGINT`。需要显式注册信号处理器。 **修复** (`main.py`): ```python import signal def _run_qt(self) -> None: # ... def _sigint_handler(signum, frame): print("\n[EzVibe] 收到 Ctrl+C,正在关闭 ...") app.quit() signal.signal(signal.SIGINT, _sigint_handler) app.exec() ``` 同时在 `main.py` 开头(在任何 Qt 导入之前)设置平台环境变量: ```python if sys.platform.startswith("linux") and os.environ.get("QT_QPA_PLATFORM") is None: os.environ["QT_QPA_PLATFORM"] = "xcb" ``` **原因**:Fedora 43 默认 Wayland,PySide6 如果找不到 XCB 平台插件会静默失败(窗口根本不创建)。`QT_QPA_PLATFORM` 必须在任何 Qt 模块导入之前设置。 --- ### 第 2 轮:`DummyPetWindow` API 不兼容 **问题**:`python3 main.py --dummy` 正常,但全量模式报错: ``` AttributeError: 'DummyPetWindow' object has no attribute 'isVisible' ``` `DummyPetWindow` 用的是 snake_case 方法名(`is_visible()`),但 `main.py` 里调用的是 Qt 风格的 camelCase(`isVisible()`)。 **修复** (`ui/pet_window.py`): ```python def is_visible(self) -> bool: return self._visible # Qt API 别名 isVisible = is_visible ``` 同时补充 `geometry()` 方法(同样被 `main.py` 调试输出调用): ```python def geometry(self): """返回模拟的 QRect。""" class _DummyRect: def __init__(self, x, y, w, h): self._x, self._y, self._w, self._h = x, y, w, h def x(self): return self._x def y(self): return self._y def width(self): return self._w def height(self): return self._h def right(self): return self._x + self._w def top(self): return self._y def __repr__(self): return f"QRect({self._x}, {self._y}, {self._w}, {self._h})" return _DummyRect(self._position[0], self._position[1], 200, 200) ``` **原则**:保持 `PetWindow`(真 Qt)和 `DummyPetWindow`(headless)API 完全兼容,避免调用方因类型不同而崩溃。 --- ### 第 3 轮:`_load_qt()` 检测逻辑错误 **问题**:`create_pet_window()` 总是返回 `DummyPetWindow`,从未成功创建真正的 Qt 窗口。 **根因**:`__import__()` 的行为: ```python # 旧代码(错误) mod = __import__("PySide6") # 返回顶层级: getattr(mod, "QtCore") # PySide6 没有 QtCore 属性 → 抛出 AttributeError # → _load_qt() 返回 None → 回退到 DummyPetWindow ``` `__import__("PySide6")` 只返回顶级模块,不会加载子模块 `QtCore`/`QtWidgets`。所以 `getattr(mod, "QtCore")` 永远失败。 **修复** (`ui/pet_window.py`): ```python def _load_qt() -> str | None: global _Qt if _Qt is not None: return _Qt for lib in ("PySide6", "PyQt6"): try: # 直接 import 子模块,fromlist=[""] 触发完整路径导入 __import__(f"{lib}.QtCore", fromlist=[""]) __import__(f"{lib}.QtWidgets", fromlist=[""]) _Qt = lib return lib except (ImportError, AttributeError): continue return None ``` **关键**:`fromlist=[""]` 使得 `__import__()` 返回最右边的模块(即 `QtCore`/`QtWidgets`),真正触发子模块的加载和验证。 --- ### 第 4 轮:`QApplication` 未创建时创建了 `QWidget` **问题**: ``` QWidget: Must construct a QApplication before a QWidget Aborted (core dumped) ``` **根因**:之前的架构中,`create_pet_window()` 在 `EzVibeApp.__init__()` 里被调用,而 `QApplication` 在 `_run_qt()` 里才创建。Qt 要求所有 widget 必须在 `QApplication` 之后才能实例化。 **修复** (`main.py`): ```python def __init__(...): # ... # 6. 桌宠窗口(延迟创建,见 _run_qt) self._window = None # 不再在这里调用 create_pet_window def _run_qt(self) -> None: app = QtWidgets.QApplication(sys.argv) # ... # ★ 现在 QApplication 已存在,创建桌宠窗口 from ui.pet_window import create_pet_window self._window = create_pet_window( emotion_engine=self._emotion, brain=self._brain, scheduler=self._scheduler, assets_dir=self._assets_dir, force_dummy=self._use_dummy, ) self._window.show() app.exec() ``` **关键**:Qt 的 `QApplication` 是全局单例,必须是第一个被创建的 Qt 对象,之后才能实例化任何 widget。这是 Qt 的硬性要求,与平台无关。 --- ## 最终结果 ``` [EzVibe] 初始化组件 ... [✓] EmotionEngine: idle [✓] VectorMemory initialized [✓] AgentBrain: dummy [✓] KeyboardMouseMonitor started [✓] BehaviorScheduler initialized [EzVibe] 启动中 ... (mode=full) [EzVibe] Qt 平台: xcb [✓] PetWindow: PetWindow (Qt=PySide6) [EzVibe] 桌宠已显示。右键点击切换情绪。 [EzVibe] 按 Ctrl+C 或关闭窗口退出。 ``` 窗口成功显示在屏幕上。 ## Linux 部署检查清单 如果在新环境部署,按以下顺序检查: 1. **安装 PySide6**:`pip install PySide6` 2. **安装 Qt6 XCB 平台插件**: - Fedora: `sudo dnf install qt6-qtbase-xcb` - Ubuntu/Debian: `sudo apt install libxkbcommon-x11-0`(XCB 依赖) 3. **验证 Qt 平台**:`python3 -c "from PySide6 import QtGui; print(QtGui.QGuiApplication.platformName())"` - 期望输出:`xcb` - 如果输出 `offscreen` 或报错,说明 XCB 插件未装好 4. **测试窗口**: ```bash python3 -c " import os, sys os.environ['QT_QPA_PLATFORM'] = 'xcb' from PySide6 import QtWidgets app = QtWidgets.QApplication(sys.argv) w = QtWidgets.QWidget() w.setStyleSheet('background: rgba(200,100,100,200);') w.resize(200,200) w.show() app.exec() ``` - 如果有任何错误,检查第 2 步 5. **运行桌宠**:`python3 main.py` 6. **退出**:`Ctrl+C` 或关闭窗口 ## 修改文件清单 | 文件 | 修改内容 | |------|---------| | `main.py` | 早期设置 `QT_QPA_PLATFORM=xcb`;延迟 PetWindow 创建到 `QApplication` 之后;SIGINT 处理器;调试输出 | | `ui/pet_window.py` | 修复 `_load_qt()` 使用正确的 `__import__` 方式;`DummyPetWindow` 添加 `isVisible` 别名和 `geometry()` 方法 |