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
相关产品推荐
相关产品推荐

