Screen Capture Example

Screen Capture demonstrates how to capture a screen or window using QScreenCapture and QWindowCapture. The example shows a list of screens and windows and displays a live preview of the selected item using a QMediaCaptureSession and a QVideoWidget. Capturing can be started and stopped with a QPushButton.

Application Structure

The example consists of three custom classes. The UI and all screen capture functionality is implemented in the class ScreenCapturePreview. The classes ScreenListModel and WindowListModel only serve as models behind the two QListView widgets. The main function creates a ScreenCapturePreview object, which in turn creates instances of QScreenCapture and QWindowCapture, and a QMediaCaptureSession and QVideoWidget, in addition to all the UI widgets.

The screen and window models are populated with the return values of QGuiApplication.screens() and QWindowCapture.capturableWindows(), respectively.

When a list item is selected, it is connected to the QScreenCapture object with QScreenCapture.setScreen(), or to the QWindowCapture object with QWindowCapture.setWindow(). The capture object is connected to the QMediaCaptureSession object with QMediaCaptureSession.setScreenCapture() and QMediaCaptureSession.setWindowCapture(), respectively. The capture session in turn is connected to the QVideoWidget object with QMediaCaptureSession.setVideoOutput(). Thus, the capture output is previewed in the video widget on the right hand side of the UI.

The start/stop button calls QScreenCapture.start() and QScreenCapture.stop(), or QWindowCapture.start() and QWindowCapture.stop().

A QMessageBox pops up if an errorOccurred signal is emitted.

Download this example

# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

"""PySide6 port of the QtMultiMedia Screen Capture Example from Qt v6.x"""

import sys

from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QApplication

from screencapturepreview import ScreenCapturePreview


if __name__ == "__main__":
    app = QApplication(sys.argv)
    QCoreApplication.setApplicationName("screencapture")
    QCoreApplication.setOrganizationName("QtProject")
    screen_capture_preview = ScreenCapturePreview()
    screen_capture_preview.show()
    sys.exit(app.exec())
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from enum import Enum, auto

from PySide6.QtMultimediaWidgets import QVideoWidget
from PySide6.QtMultimedia import (QCapturableWindow, QMediaCaptureSession,
                                  QScreenCapture, QWindowCapture)
from PySide6.QtWidgets import (QGridLayout, QLabel, QListView,
                               QMessageBox, QPushButton, QWidget)
from PySide6.QtGui import QAction, QGuiApplication
from PySide6.QtCore import QItemSelection, Qt, Slot

from screenlistmodel import ScreenListModel
from windowlistmodel import WindowListModel


class SourceType(Enum):
    Screen = auto()
    Window = auto()


class ScreenCapturePreview(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)

        self._source = SourceType.Screen

        self._screen_capture = QScreenCapture(self)
        self._media_capture_session = QMediaCaptureSession(self)
        self._video_widget = QVideoWidget(self)
        self._screen_list_view = QListView(self)
        self._screen_label = QLabel("Select screen to capture:", self)
        self._video_widget_label = QLabel("Capture output:", self)
        self._start_stop_button = QPushButton(self)
        self._status_label = QLabel(self)

        self._screen_list_model = ScreenListModel(self)

        # Setup QScreenCapture with initial source:
        self.setScreen(QGuiApplication.primaryScreen())
        self._screen_capture.start()
        self._media_capture_session.setScreenCapture(self._screen_capture)
        self._media_capture_session.setVideoOutput(self._video_widget)

        self._screen_list_view.setModel(self._screen_list_model)

        self._window_list_view = QListView(self)
        self._window_capture = QWindowCapture(self)
        self._media_capture_session.setWindowCapture(self._window_capture)
        self._window_label = QLabel("Select window to capture:", self)

        self._window_list_model = WindowListModel(self)
        self._window_list_view.setModel(self._window_list_model)
        update_action = QAction("Update windows List", self)
        update_action.triggered.connect(self._window_list_model.populate)
        self._window_list_view.addAction(update_action)
        self._window_list_view.setContextMenuPolicy(Qt.ActionsContextMenu)

        grid_layout = QGridLayout(self)
        grid_layout.addWidget(self._screen_label, 0, 0)
        grid_layout.addWidget(self._screen_list_view, 1, 0)
        grid_layout.addWidget(self._start_stop_button, 4, 0)
        grid_layout.addWidget(self._video_widget_label, 0, 1)
        grid_layout.addWidget(self._video_widget, 1, 1, 4, 1)
        grid_layout.addWidget(self._window_label, 2, 0)
        grid_layout.addWidget(self._window_list_view, 3, 0)
        grid_layout.addWidget(self._status_label, 5, 0, 1, 2)

        grid_layout.setColumnStretch(1, 1)
        grid_layout.setRowStretch(1, 1)
        grid_layout.setColumnMinimumWidth(0, 400)
        grid_layout.setColumnMinimumWidth(1, 400)
        grid_layout.setRowMinimumHeight(3, 1)

        selection_model = self._screen_list_view.selectionModel()
        selection_model.selectionChanged.connect(self.on_current_screen_selection_changed)
        selection_model = self._window_list_view.selectionModel()
        selection_model.selectionChanged.connect(self.on_current_window_selection_changed)

        self._start_stop_button.clicked.connect(self.on_start_stop_button_clicked)
        self._screen_capture.errorOccurred.connect(self.on_screen_capture_error_occured,
                                                   Qt.QueuedConnection)
        self._window_capture.errorOccurred.connect(self.on_window_capture_error_occured,
                                                   Qt.QueuedConnection)
        self.update_active(SourceType.Screen, True)

    @Slot(QItemSelection)
    def on_current_screen_selection_changed(self, selection):
        self.clear_error_string()
        indexes = selection.indexes()
        if indexes:
            self._screen_capture.setScreen(self._screen_list_model.screen(indexes[0]))
            self.update_active(SourceType.Screen, self.is_active())
            self._window_list_view.clearSelection()
        else:
            self._screen_capture.setScreen(None)

    @Slot(QItemSelection)
    def on_current_window_selection_changed(self, selection):
        self.clear_error_string()
        indexes = selection.indexes()
        if indexes:
            window = self._window_list_model.window(indexes[0])
            if not window.isValid():
                m = "The window is no longer valid. Update the list of windows?"
                answer = QMessageBox.question(self, "Invalid window", m)
                if answer == QMessageBox.Yes:
                    self.update_active(SourceType.Window, False)
                    self._window_list_view.clearSelection()
                    self._window_list_model.populate()
                    return
            self._window_capture.setWindow(window)
            self.update_active(SourceType.Window, self.is_active())
            self._screen_list_view.clearSelection()
        else:
            self._window_capture.setWindow(QCapturableWindow())

    @Slot(QWindowCapture.Error, str)
    def on_window_capture_error_occured(self, error, error_string):
        self.set_error_string("QWindowCapture: Error occurred " + error_string)

    @Slot(QScreenCapture.Error, str)
    def on_screen_capture_error_occured(self, error, error_string):
        self.set_error_string("QScreenCapture: Error occurred " + error_string)

    def set_error_string(self, t):
        self._status_label.setStyleSheet("background-color: rgb(255, 0, 0);")
        self._status_label.setText(t)

    def clear_error_string(self):
        self._status_label.clear()
        self._status_label.setStyleSheet("")

    @Slot()
    def on_start_stop_button_clicked(self):
        self.clear_error_string()
        self.update_active(self._source_type, not self.is_active())

    def update_start_stop_button_text(self):
        active = self.is_active()
        if self._source_type == SourceType.Window:
            m = "Stop window capture" if active else "Start window capture"
            self._start_stop_button.setText(m)
        elif self._source_type == SourceType.Screen:
            m = "Stop screen capture" if active else "Start screen capture"
            self._start_stop_button.setText(m)

    def update_active(self, source_type, active):
        self._source_type = source_type
        self._screen_capture.setActive(active and source_type == SourceType.Screen)
        self._window_capture.setActive(active and source_type == SourceType.Window)

        self.update_start_stop_button_text()

    def is_active(self):
        if self._source_type == SourceType.Window:
            return self._window_capture.isActive()
        if self._source_type == SourceType.Screen:
            return self._screen_capture.isActive()
        return False
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from PySide6.QtGui import QGuiApplication
from PySide6.QtCore import QAbstractListModel, Qt, Slot


class ScreenListModel(QAbstractListModel):

    def __init__(self, parent=None):
        super().__init__(parent)
        app = qApp  # noqa: F821
        app.screenAdded.connect(self.screens_changed)
        app.screenRemoved.connect(self.screens_changed)
        app.primaryScreenChanged.connect(self.screens_changed)

    def rowCount(self, index):
        return len(QGuiApplication.screens())

    def data(self, index, role):
        screen_list = QGuiApplication.screens()

        if role == Qt.ItemDataRole.DisplayRole:
            screen = screen_list[index.row()]
            w = screen.size().width()
            h = screen.size().height()
            dpi = screen.logicalDotsPerInch()
            return f'"{screen.name()}" {w}x{h}, {dpi}DPI'

        return None

    def screen(self, index):
        return QGuiApplication.screens()[index.row()]

    @Slot()
    def screens_changed(self):
        self.beginResetModel()
        self.endResetModel()
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from PySide6.QtCore import QAbstractListModel, Qt, Slot
from PySide6.QtMultimedia import QWindowCapture


class WindowListModel(QAbstractListModel):

    def __init__(self, parent=None):
        super().__init__(parent)
        self._window_list = QWindowCapture.capturableWindows()

    def rowCount(self, QModelIndex):
        return len(self._window_list)

    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            window = self._window_list[index.row()]
            return window.description()
        return None

    def window(self, index):
        return self._window_list[index.row()]

    @Slot()
    def populate(self):
        self.beginResetModel()
        self._window_list = QWindowCapture.capturableWindows()
        self.endResetModel()