[英]How to detect when a foreign window embedded with QWidget.createWindowContainer closes itself?
我正在使用PySide2.QtGui.QWindow.fromWinId(windowId)
将另一个 window 嵌入到 Qt 小部件中。 它运行良好,但当原始 X11 window 破坏它时,它不会触发事件。
如果我使用mousepad & python3 embed.py
运行下面的文件并按Ctrl + Q ,则不会触发任何事件,并且我只剩下一个空小部件。
如何检测由QWindow.fromWinId
导入的 X11 window 何时被其创建者销毁?
#!/usr/bin/env python
# sudo apt install python3-pip
# pip3 install PySide2
import sys, subprocess, PySide2
from PySide2 import QtGui, QtWidgets, QtCore
class MyApp(QtCore.QObject):
def __init__(self):
super(MyApp, self).__init__()
# Get some external window's windowID
print("Click on a window to embed it")
windowIdStr = subprocess.check_output(['sh', '-c', """xwininfo -int | sed -ne 's/^.*Window id: \\([0-9]\\+\\).*$/\\1/p'"""]).decode('utf-8')
windowId = int(windowIdStr)
print("Embedding window with windowId=" + repr(windowId))
# Create a simple window frame
self.app = QtWidgets.QApplication(sys.argv)
self.mainWindow = QtWidgets.QMainWindow()
self.mainWindow.show()
# Grab the external window and put it inside our window frame
self.externalWindow = QtGui.QWindow.fromWinId(windowId)
self.externalWindow.setFlags(QtGui.Qt.FramelessWindowHint)
self.container = QtWidgets.QWidget.createWindowContainer(self.externalWindow)
self.mainWindow.setCentralWidget(self.container)
# Install event filters on all Qt objects
self.externalWindow.installEventFilter(self)
self.container.installEventFilter(self)
self.mainWindow.installEventFilter(self)
self.app.installEventFilter(self)
self.app.exec_()
def eventFilter(self, obj, event):
# Lots of events fire, but no the Close one
print(str(event.type()))
if event.type() == QtCore.QEvent.Close:
mainWindow.close()
return False
prevent_garbage_collection = MyApp()
下面是一个简单的演示脚本,展示了如何检测嵌入式外部 window 何时关闭。 该脚本仅适用于 Linux/X11。 要运行它,您必须安装wmctrl 。 解决方案本身根本不依赖 wmctrl:它只是用来从进程 ID 中获取 window ID; 我只在我的演示脚本中使用了它,因为它的 output 非常容易解析。
实际的解决方案依赖于QProcess 。 这用于启动外部程序,然后它的完成信号通知主 window 程序已关闭。 目的是这种机制应该取代您当前使用子进程和轮询的方法。 这两种方法的主要限制是它们不适用于将自身作为后台任务运行的程序。 然而,我在我的 Arch Linux 系统上测试了我的脚本——包括 Inkscape、GIMP、GPicView、SciTE、Konsole 和 SMPlayer——它们的行为都符合预期(即它们在退出时关闭了容器 window)。
注意:为了使演示脚本正常工作,可能需要在某些程序中禁用闪屏等,以便它们可以正确嵌入。 例如,GIMP 必须像这样运行:
$ python demo_script.py gimp -s
如果脚本抱怨找不到程序 ID,这可能意味着程序作为后台任务启动了自己,因此您必须尝试找到某种方法将其强制到前台。
免责声明:上述解决方案可能适用于其他平台,但我没有在那里测试过,因此无法提供任何保证。 我也不能保证它适用于 Linux/X11 上的所有程序。
我还应该指出,嵌入外部的第三方 windows 不受 ZE8801102A40AD89DDCFCFDCAEBF008D25Z 的官方支持。 The createWindowContainer function is only intended to work with Qt window IDs, so the behaviour with foreign window IDs is strictly undefined (see: QTBUG-44404 ). 这篇 wiki 文章记录了各种问题: Qt 和国外 windows 。 特别是, 它指出:
我们当前 API 的一个更大的问题(尚未讨论)是 QWindow::fromWinId() 返回一个 QWindow 指针,从 API 合约的角度来看,它应该支持任何其他 QWindow 支持的任何操作,包括使用设置器操作 window,并连接到信号以观察 window 的变化。
我们的任何平台在实践中都没有遵守这个合同,并且 QWindow::fromWinId() 的文档没有提到任何关于这种情况的内容。
这种未定义/平台特定行为的原因很大程度上归结为我们的平台依赖于完全控制本机 window 句柄,而本机 window 句柄通常是本机 window 句柄的子类,我们在其中实现了其他逻辑。 当用我们无法控制的实例替换本机 window 句柄时,它不实现我们的回调逻辑,与常规 QWindow 相比,行为变得未定义且充满漏洞。
因此,在设计依赖此功能的应用程序时,请牢记所有这些,并相应地调整您的期望......
演示脚本:
import sys, os, shutil
from PySide2.QtCore import (
Qt, QProcess, QTimer,
)
from PySide2.QtGui import (
QWindow,
)
from PySide2.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QMessageBox,
)
class Window(QWidget):
def __init__(self, program, arguments):
super().__init__()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
self.external = QProcess(self)
self.external.start(program, arguments)
self.wmctrl = QProcess()
self.wmctrl.setProgram('wmctrl')
self.wmctrl.setArguments(['-lpx'])
self.wmctrl.readyReadStandardOutput.connect(self.handleReadStdOut)
self.timer = QTimer(self)
self.timer.setSingleShot(True)
self.timer.setInterval(25)
self.timer.timeout.connect(self.wmctrl.start)
self.timer.start()
self._tries = 0
def closeEvent(self, event):
for process in self.external, self.wmctrl:
process.terminate()
process.waitForFinished(1000)
def embedWindow(self, wid):
window = QWindow.fromWinId(wid)
widget = QWidget.createWindowContainer(
window, self, Qt.FramelessWindowHint)
self.layout().addWidget(widget)
def handleReadStdOut(self):
pid = self.external.processId()
if pid > 0:
windows = {}
for line in bytes(self.wmctrl.readAll()).decode().splitlines():
columns = line.split(maxsplit=5)
# print(columns)
# wid, desktop, pid, wmclass, client, title
windows[int(columns[2])] = int(columns[0], 16)
if pid in windows:
self.embedWindow(windows[pid])
# this is where the magic happens...
self.external.finished.connect(self.close)
elif self._tries < 100:
self._tries += 1
self.timer.start()
else:
QMessageBox.warning(self, 'Error',
'Could not find WID for PID: %s' % pid)
else:
QMessageBox.warning(self, 'Error',
'Could not find PID for: %r' % self.external.program())
if __name__ == '__main__':
if len(sys.argv) > 1:
if shutil.which(sys.argv[1]):
app = QApplication(sys.argv)
window = Window(sys.argv[1], sys.argv[2:])
window.setGeometry(100, 100, 800, 600)
window.show()
sys.exit(app.exec_())
else:
print('could not find program: %r' % sys.argv[1])
else:
print('usage: python %s <external-program-name> [args]' %
os.path.basename(__file__))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.