即使窗口重叠,我如何在Qt(Python,Linux)中截取特定窗口的屏幕截图?

问题描述 投票:0回答:1

我正在尝试截取PyQt5中当前活动窗口的屏幕截图。我知道拍摄任何窗口的屏幕快照的通用方法是QScreen::grabWindow(winID),对此,winIDimplementation-specific ID depending on the window system。由于我正在运行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()

为了测试实现,我启动了脚本并单击了另一个窗口。如果我的应用程序窗口和活动窗口之间没有重叠,它似乎可以正常工作。请参见以下屏幕截图,选择Firefox(右)后,我的应用程序便能够捕获Firefox的活动窗口并将其显示在QLabel中。

When Firefox (right) is selected, my application is able to capture the active window of Firefox and display it in the QLabel.

但是,如果应用程序窗口和活动窗口之间存在重叠,则屏幕截图将无法正常工作。应用程序本身的窗口将被捕获,并创建一个积极的反馈。

如果应用程序窗口和活动窗口之间存在重叠。应用程序本身的窗口将被捕获,并创建一个积极的反馈。

“”

我已经在KDE的设置中禁用了3D合成,但是问题仍然存在。上面的示例在禁用所有复合效果的情况下进行。

问题

  1. 当应用程序窗口和活动窗口重叠时,为什么此实现无法正常工作?我怀疑这是图形系统之间某些形式的不必要交互(Qt工具包,窗口管理器,X等)引起的问题,但我不确定。

  2. 是否有可能解决这个问题? (注意:我知道我可以在屏幕截图之前单击hide(),然后再次单击show(),但是它并不能真正解决此问题,即使存在重叠也需要截图)。

python linux pyqt5 screenshot x11
1个回答
0
投票

正如@eyllanesc指出的那样,似乎不可能在Qt中执行此操作,至少不能使用QScreen::grabWindow执行此操作,因为grabWindow()实际上并没有抓住窗口本身,而只是抓住了窗口占用的区域。窗口。 The documentation包含以下警告。

[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()

现在,即使顶部有重叠的窗口,它也可以完美捕获活动窗口。

Now it captures an active window perfectly, even if there are overlapping windows on top of it.

© www.soinside.com 2019 - 2024. All rights reserved.