Document portal capture setup, frame lifetime and deferred shutdown

This commit is contained in:
evgeny committed 2026-10-28 00:58:59 +01:00
1 parent d9eab97dee
commit 3960e28638
1 file changed
+26 -10
+26 -10
View File
@@ -1,6 +1,11 @@
/* Захват Wayland: ScreenCast portal выбирает монитор и встраивает курсор, PipeWire отдаёт кадры.
* Объект и callbacks работают в GUI-потоке; pw_loop обслуживается QSocketNotifier без фонового worker.
* Режим курсора задаётся до start. Деструктор отменяет запрос, закрывает поток и сессию. */
/* Однократный захват монитора через ScreenCast portal и PipeWire, в том числе системный выбор Wayland.
* Создать в GUI-потоке, подключить сигналы, один раз вызвать start. Все методы/сигналы — тот же Qt-поток;
* pw_loop обслуживается QSocketNotifier без собственного worker. Нужны session D-Bus и совместимый portal.
* Portal выбирает ровно один монитор и режим Hidden/Embedded; режим курсора неизменен на всю сессию.
* Кадры копируются из последнего доступного packed RGB-буфера; старые буферы сразу возвращаются PipeWire.
* Ошибка откладывает очистку и errorOccurred за пределы PipeWire callback. Повторный start не поддерживается.
* Деструктор освобождает локальный поток и посылает Request/Session.Close без ожидания ответа.
* Контракт portal: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html */
#pragma once
#include <QObject>
#include <QDBusMessage>
@@ -11,22 +16,33 @@
class StreamPortalCapture : public QObject {
Q_OBJECT
public:
// parent владеет объектом по правилам QObject; создание не начинает системный выбор экрана.
explicit StreamPortalCapture(QObject* parent = nullptr);
// GUI-поток, вне frameReady/PipeWire callbacks: из обработчика кадра использовать deleteLater.
~StreamPortalCapture() override;
// Начать асинхронный выбор: showCursor=true требует Embedded=2, false — Hidden=1; fallback режима отсутствует.
// На служебные этапы отведено 30с, на первый кадр 8с; ожидание пользователя на Start не ограничено.
// Успех возвращается через frameReady; отказ/отмена выбора/повторный start — через errorOccurred.
void start(bool showCursor);
signals:
// Скопированный QVideoFrame; ссылка заимствована на время сигнала, для сохранения скопировать объект.
// PTS из PipeWire переводится нс→мкс; при отсутствии допустимого PTS остаётся значение Qt по умолчанию.
// Не уничтожать capture синхронно из этого сигнала: PipeWire-буфер возвращается после выхода из обработчиков.
void frameReady(const QVideoFrame& frame);
// Единственная терминальная ошибка с причиной, после очистки; для новой попытки нужен новый объект.
void errorOccurred(const QString& error);
private slots:
// Обработать только Response текущего request path; старые ответы/состояние Failed игнорируются.
void response(uint code, const QVariantMap& results, const QDBusMessage& message);
// Системное Session.Closed завершает захват ошибкой.
void sessionClosed();
private:
struct Impl;
std::unique_ptr<Impl> m;
void request(const QString& method, QVariantList args, QVariantMap options);
void openRemote(uint node, const QString& serial);
void openPipeWire(int fd, uint node, const QString& serial);
void processFrame();
void fail(const QString& reason);
void close();
std::unique_ptr<Impl> m; // Единственный владелец D-Bus-состояния, таймера, notifier и объектов PipeWire.
void request(const QString& method, QVariantList args, QVariantMap options); // Сначала подписаться, затем отправить D-Bus-вызов.
void openRemote(uint node, const QString& serial); // Получить fd сессии для выбранного node/необязательного serial.
void openPipeWire(int fd, uint node, const QString& serial); // Дублировать заимствованный fd и открыть собственный поток.
void processFrame(); // В PipeWire process: взять последний буфер, проверить размеры, скопировать и вернуть буфер.
void fail(const QString& reason); // Первый отказ: пометить Failed, отложенно close и сообщить переводимую ошибку.
void close(); // Идемпотентная очистка; выполнять вне callbacks PipeWire, состояние остаётся Failed.
};