6.9 KiB
Linux Qt 桌面宠物体修复记录
问题描述
Fedora 43 上运行 python3 main.py 时:
- Ctrl-C 无法停止 — 程序忽略 SIGINT,窗口也看不见
- 无任何窗口出现 —
python3 main.py没有任何输出就崩溃或无响应 - 错误信息模糊 — 无明确报错,难以定位根因
修复过程(4 轮迭代)
第 1 轮:包名错误 + Ctrl-C 无法停止
问题 1:包名错误
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):
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 导入之前)设置平台环境变量:
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):
def is_visible(self) -> bool:
return self._visible
# Qt API 别名
isVisible = is_visible
同时补充 geometry() 方法(同样被 main.py 调试输出调用):
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__() 的行为:
# 旧代码(错误)
mod = __import__("PySide6") # 返回顶层级:<module 'PySide6'>
getattr(mod, "QtCore") # PySide6 没有 QtCore 属性 → 抛出 AttributeError
# → _load_qt() 返回 None → 回退到 DummyPetWindow
__import__("PySide6") 只返回顶级模块,不会加载子模块 QtCore/QtWidgets。所以 getattr(mod, "QtCore") 永远失败。
修复 (ui/pet_window.py):
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):
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 部署检查清单
如果在新环境部署,按以下顺序检查:
- 安装 PySide6:
pip install PySide6 - 安装 Qt6 XCB 平台插件:
- Fedora:
sudo dnf install qt6-qtbase-xcb - Ubuntu/Debian:
sudo apt install libxkbcommon-x11-0(XCB 依赖)
- Fedora:
- 验证 Qt 平台:
python3 -c "from PySide6 import QtGui; print(QtGui.QGuiApplication.platformName())"- 期望输出:
xcb - 如果输出
offscreen或报错,说明 XCB 插件未装好
- 期望输出:
- 测试窗口:
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 步
- 运行桌宠:
python3 main.py - 退出:
Ctrl+C或关闭窗口
修改文件清单
| 文件 | 修改内容 |
|---|---|
main.py |
早期设置 QT_QPA_PLATFORM=xcb;延迟 PetWindow 创建到 QApplication 之后;SIGINT 处理器;调试输出 |
ui/pet_window.py |
修复 _load_qt() 使用正确的 __import__ 方式;DummyPetWindow 添加 isVisible 别名和 geometry() 方法 |