簡體   English   中英

即使窗口重疊,如何在 Qt(Python、Linux)中截取特定窗口的屏幕截圖?

[英]How do I take a screenshot of a specfic window in Qt (Python, Linux), even if the windows are overlapped?

我正在嘗試截取 PyQt5 中當前活動窗口的屏幕截圖。 我知道截取任何窗口屏幕截圖的通用方法是QScreen::grabWindow(winID) ,其中winID特定於實現的 ID,具體取決於窗口系統 由於我正在運行 X 和 KDE,我計划最終使用 CTypes 來調用 Xlib,但現在,我只是執行“xdotool getactivewindow”來獲取 shell 中的 windowID。

對於最小的例子,我創建了一個帶有 QTimer 的 QMainWindow。 當定時器被觸發時,我通過執行“xdotool getactivewindow”來識別活動窗口ID,獲取其返回值,調用grabWindow()來捕獲活動窗口,並在QLabel中顯示截圖。 在啟動時,我還將我的窗口設置為固定的 500x500 大小以進行觀察,並激活Qt.WindowStaysOnTopHint標志,以便我的窗口在未聚焦時仍然可見。 把它們放在一起,實現就是下面的代碼。

from PyQt5 import QtCore, QtGui, QtWidgets
import subprocess


class ScreenCapture(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
        self.setFixedHeight(500)
        self.setFixedWidth(500)

        self.label = QtWidgets.QLabel(self)

        self.timer = QtCore.QTimer(self)
        self.timer.setInterval(500)
        self.timer.timeout.connect(self.timer_handler)
        self.timer.start()

        self.screen = QtWidgets.QApplication.primaryScreen()

    @QtCore.pyqtSlot()
    def timer_handler(self):
        window = int(subprocess.check_output(["xdotool", "getactivewindow"]).decode("ascii"))
        self.screenshot = self.screen.grabWindow(window)

        self.label.setPixmap(self.screenshot)
        self.label.setFixedSize(self.screenshot.size())


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    window = ScreenCapture()
    window.show()
    app.exec()

為了測試實現,我啟動了腳本並單擊了另一個窗口。 如果我的應用程序窗口和活動窗口之間沒有重疊,它似乎可以正常工作。 See the following screenshot, when Firefox (right) is selected, my application is able to capture the active window of Firefox and display it in the QLabel.

選擇Firefox(右)時,我的應用程序能夠捕獲Firefox的活動窗口並在QLabel中顯示它。

但是,如果應用程序窗口和活動窗口之間存在重疊,屏幕截圖將不會按預期工作。 應用程序本身的窗口將被捕獲,並產生積極的反饋。

如果應用程序窗口和活動窗口之間有重疊。 應用程序本身的窗口將被捕獲,並產生積極的反饋。

我已經在 KDE 的設置中禁用了 3D 合成,但問題仍然存在。 上面的例子是在禁用所有復合效果的情況下進行的。

  1. 當應用程序窗口和活動窗口重疊時,為什么這個實現不能正常工作? 我懷疑這是由圖形系統(Qt 工具包、窗口管理器、X 等)之間的某種形式的不需要的交互引起的問題,但我不確定。

  2. 甚至有可能解決這個問題嗎? (注意:我知道我可以在屏幕截圖之前hide()並再次show() ,但它並沒有真正解決這個問題,即使存在重疊也會截圖。)

正如@eyllanesc 所指出的,似乎在 Qt 中不可能做到這一點,至少用QScreen::grabWindow ,因為grabWindow()實際上並沒有抓住窗口本身,而只是抓住了窗口占用的區域. 該文檔包含以下警告。

grabWindow() 函數從屏幕上而不是從窗口中抓取像素,即如果有另一個窗口部分或全部覆蓋在您抓取的窗口上,您也會從上層窗口獲取像素。 鼠標光標一般不會被抓取。

結論是,在純 Qt 中是不可能做到的。 只能通過編寫低級 X 程序來實現這樣的功能。 由於該問題要求“在 Qt 中”提供解決方案,因此任何可能涉及更深層次、低級 X 解決方案的答案都超出了范圍。 這個問題可以標記為已解決。

在這里學到的教訓:在使用函數或方法之前,請務必查看文檔。


更新:我設法通過 Xlib 直接從 X 讀取窗口來解決問題。 有點諷刺的是,我的解決方案使用 GTK 來抓取窗口並將其結果發送到 Qt...無論如何,如果你不想使用 GTK,你可以直接用 Xlib 編寫相同的程序,但我使用 GTK,因為 Xlib 相關GDK 中的函數非常方便地演示基本概念。

要獲取屏幕截圖,我們首先將窗口 ID 轉換為適合在 GDK 中使用的GdkWindow ,然后調用Gdk.pixbuf_get_from_window()獲取窗口並將其存儲在gdk_pixbuf 最后,我們調用save_to_bufferv()將原始 pixbuf 轉換為合適的圖像格式並將其存儲在緩沖區中。 此時,緩沖區中的圖像適合在任何程序中使用,包括 Qt。

該文檔包含以下警告:

如果窗口在屏幕外,則在被遮擋/屏幕外區域中沒有圖像數據要放置在 pixbuf 中。 與屏幕外區域對應的 pixbuf 部分的內容未定義。

如果您從中獲取數據的窗口被其他窗口部分遮擋,則與遮擋區域對應的 pixbuf 區域的內容未定義。

如果窗口沒有被映射(通常是因為它被圖標化/最小化或不在當前工作區中),則將返回 NULL。

如果無法為返回值分配內存,則將返回 NULL。

它也有一些關於合成的評論,

gdk_display_supports_composite自 3.16 版起已棄用,不應在新編寫的代碼中使用。

合成是一種過時的技術,只適用於 X11。

所以基本上,只能使用合成窗口管理器在 X11 下抓取部分遮擋的窗口(在 Wayland 中不可能!)。 我在沒有合成的情況下對其進行了測試,發現禁用合成時窗口變黑。 但是當啟用組合時,它似乎沒有問題。 它可能適用於您的應用程序,也可能不適用。 但是我認為如果您在 X11 下使用合成,它可能會起作用。

from PyQt5 import QtCore, QtGui, QtWidgets
import subprocess


class ScreenCapture(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
        self.setFixedHeight(500)
        self.setFixedWidth(500)

        self.label = QtWidgets.QLabel(self)
        self.screen = QtWidgets.QApplication.primaryScreen()

        self.timer = QtCore.QTimer(self)
        self.timer.setInterval(500)
        self.timer.timeout.connect(self.timer_handler)
        self.timer.start()

    @staticmethod
    def grab_screenshot():
        from gi.repository import Gdk, GdkX11

        window_id = int(subprocess.check_output(["xdotool", "getactivewindow"]).decode("ascii"))

        display = GdkX11.X11Display.get_default()
        window = GdkX11.X11Window.foreign_new_for_display(display, window_id)

        x, y, width, height = window.get_geometry()
        pb = Gdk.pixbuf_get_from_window(window, 0, 0, width, height)

        if pb:
            buf = pb.save_to_bufferv("bmp", (), ())
            return buf[1]
        else:
            return

    @QtCore.pyqtSlot()
    def timer_handler(self):
        screenshot = self.grab_screenshot()
        self.pixmap = QtGui.QPixmap()
        if not self.pixmap:
            return

        self.pixmap.loadFromData(screenshot)
        self.label.setPixmap(self.pixmap)
        self.label.setFixedSize(self.pixmap.size())
        

if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    window = ScreenCapture()
    window.show()
    app.exec()

現在它可以完美地捕獲一個活動窗口,即使它上面有重疊的窗口。

現在它可以完美地捕獲一個活動窗口,即使它上面有重疊的窗口。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM