[英]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.