Files
EzVibe/docs/linux-qt-fix.md

6.9 KiB
Raw Permalink Blame History

Linux Qt 桌面宠物体修复记录

问题描述

Fedora 43 上运行 python3 main.py 时:

  1. Ctrl-C 无法停止 — 程序忽略 SIGINT窗口也看不见
  2. 无任何窗口出现python3 main.py 没有任何输出就崩溃或无响应
  3. 错误信息模糊 — 无明确报错,难以定位根因

修复过程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

问题 2Ctrl-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 默认 WaylandPySide6 如果找不到 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 风格的 camelCaseisVisible())。

修复 (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(真 QtDummyPetWindowheadlessAPI 完全兼容,避免调用方因类型不同而崩溃。


第 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 部署检查清单

如果在新环境部署,按以下顺序检查:

  1. 安装 PySide6pip install PySide6
  2. 安装 Qt6 XCB 平台插件
    • Fedora: sudo dnf install qt6-qtbase-xcb
    • Ubuntu/Debian: sudo apt install libxkbcommon-x11-0XCB 依赖)
  3. 验证 Qt 平台python3 -c "from PySide6 import QtGui; print(QtGui.QGuiApplication.platformName())"
    • 期望输出:xcb
    • 如果输出 offscreen 或报错,说明 XCB 插件未装好
  4. 测试窗口
    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() 方法