You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PySide2中fromWinId与createWindowContainer在Linux下嵌入窗口异常

PySide2嵌入外部窗口在Linux下显示冻结画面的解决方案

问题描述

使用PySide2开发Python GUI应用时,通过QWindow.fromWinId()和QWidget.createWindowContainer()将外部窗口嵌入自身应用的功能在Windows系统可正常运行(如嵌入画图应用并能交互操作),但在Linux系统下存在异常:能成功获取外部窗口ID并完成嵌入,但嵌入后的窗口仅显示冻结画面,无法交互且画面不更新。

原代码

import Xlib.display
from PySide2.QtGui import QWindow
from PySide2.QtWidgets import QWidget, QApplication, QPushButton, QMainWindow, QVBoxLayout


def _get_window_id_win32(title):
    import win32gui
    return win32gui.FindWindow(None, title)


def get_window_id_linux(name):
    display = Xlib.display.Display()
    root = display.screen().root
    window_ids = root.get_full_property(display.intern_atom("_NET_CLIENT_LIST"), Xlib.X.AnyPropertyType).value
    for window_id in window_ids:
        window = display.create_resource_object("window", window_id)
        window_name = window.get_wm_name()
        if window_name and name in window_name:
            return window_id


def run_app(window_id):
    app = QApplication([])

    main = QMainWindow()
    widget_central = QWidget()
    layout = QVBoxLayout()

    button = QPushButton()
    button.setText('CLICK')
    layout.addWidget(button)

    window = QWindow.fromWinId(window_id)
    widget = QWidget.createWindowContainer(window)
    layout.addWidget(widget)

    widget_central.setLayout(layout)
    main.setCentralWidget(widget_central)
    main.show()

    app.exec_()


if __name__ == "__main__":
    window_id = get_window_id_linux("Window Title") # 替换为目标窗口标题
    # window_id = _get_window_id_win32('Window Title') # Windows测试用
    if window_id:
        run_app(window_id)

问题原因

在Linux的X11环境下,直接嵌入外部窗口时,Qt默认不会自动处理外部窗口的绘制更新事件,且外部窗口的父窗口关系、绘制上下文未正确关联到Qt容器,导致画面冻结。部分应用的窗口装饰(如标题栏)也会与Qt容器产生冲突,进一步阻碍交互和更新。

解决方案

1. 调整窗口父子关系与绘制重定向

通过Xlib设置外部窗口的父窗口为Qt容器窗口,并启用XComposite扩展重定向外部窗口的绘制,确保Qt能捕获到画面更新。

2. 移除外部窗口装饰

关闭外部窗口的标题栏等装饰,避免与Qt容器的布局冲突。

修改后的代码

import Xlib.display
import Xlib.X
import Xlib.ext.composite
import Xlib.Xatom
from PySide2.QtGui import QWindow
from PySide2.QtWidgets import QWidget, QApplication, QPushButton, QMainWindow, QVBoxLayout


def _get_window_id_win32(title):
    import win32gui
    return win32gui.FindWindow(None, title)


def get_window_id_linux(name):
    display = Xlib.display.Display()
    root = display.screen().root
    window_ids = root.get_full_property(display.intern_atom("_NET_CLIENT_LIST"), Xlib.X.AnyPropertyType).value
    for window_id in window_ids:
        window = display.create_resource_object("window", window_id)
        window_name = window.get_wm_name()
        if window_name and name in window_name:
            return window_id


def remove_window_decorations(window_id):
    """移除外部窗口的装饰(标题栏等)"""
    display = Xlib.display.Display()
    x_window = display.create_resource_object("window", window_id)
    motif_hints_atom = display.intern_atom("_MOTIF_WM_HINTS")
    # 设置关闭装饰的参数
    motif_hints = [
        Xlib.Xatom.MWM_HINTS_DECORATIONS,
        0, 0, 0, 0
    ]
    x_window.change_property(motif_hints_atom, motif_hints_atom, 32, motif_hints)
    display.sync()


def run_app(window_id):
    # 初始化X11显示与Composite扩展
    display = Xlib.display.Display()
    if not display.has_extension("Composite"):
        raise RuntimeError("当前系统不支持Composite扩展,无法实现窗口嵌入更新")
    
    x_window = display.create_resource_object("window", window_id)
    composite = display.extension_composite
    # 重定向窗口绘制到off-screen缓冲区,确保Qt能捕获画面更新
    composite.RedirectWindow(x_window, Xlib.ext.composite.RedirectAutomatic)
    
    # 移除窗口装饰
    remove_window_decorations(window_id)

    app = QApplication([])

    main = QMainWindow()
    widget_central = QWidget()
    layout = QVBoxLayout()

    button = QPushButton()
    button.setText('CLICK')
    layout.addWidget(button)

    # 创建容器窗口并显示,确保获取有效XID
    container_window = QWindow()
    container_widget = QWidget.createWindowContainer(container_window)
    layout.addWidget(container_widget)
    widget_central.setLayout(layout)
    main.setCentralWidget(widget_central)
    main.show()

    # 设置外部窗口的父窗口为Qt容器
    container_xid = container_window.winId()
    x_window.reparent(container_xid, 0, 0)
    x_window.map()
    display.sync()

    # 将外部窗口包装为QWindow并绑定到容器
    external_window = QWindow.fromWinId(window_id)
    external_window.setGeometry(container_window.geometry())

    app.exec_()


if __name__ == "__main__":
    window_id = get_window_id_linux("Window Title") # 替换为目标窗口标题
    # window_id = _get_window_id_win32('Window Title') # Windows测试用
    if window_id:
        run_app(window_id)

注意事项

  • 确保系统支持XComposite扩展(大多数现代Linux桌面环境默认启用)。
  • 部分应用可能存在反嵌入机制,无法被正常嵌入,这类情况需要针对具体应用做特殊处理。
  • 嵌入后需根据Qt容器的大小调整外部窗口的尺寸,可通过监听容器的resize事件同步外部窗口大小。

内容的提问来源于stack exchange,提问作者claire-c

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 11:14:56