-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Screenshot): Add Freedesktop portal screenshot support.
This uses the system implementation of a screenshot grabber, which works in sandboxes and on wayland. On systems with a desktop environment, this will fix issues with dual-screen, wayland, and Flatpak. On more barebones systems, this should make no difference.
- Loading branch information
Showing
13 changed files
with
309 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* SPDX-License-Identifier: GPL-3.0-or-later | ||
* Copyright © 2025 The TokTok team. | ||
*/ | ||
|
||
#include "screenshot.h" | ||
|
||
#include "screenshot_dbus.h" | ||
#include "src/widget/tool/abstractscreenshotgrabber.h" | ||
|
||
AbstractScreenshotGrabber* Platform::createScreenshotGrabber(QWidget* parent) | ||
{ | ||
#if QT_CONFIG(dbus) | ||
return DBusScreenshotGrabber::create(parent); | ||
#else | ||
std::ignore = parent; | ||
return nullptr; | ||
#endif | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* SPDX-License-Identifier: GPL-3.0-or-later | ||
* Copyright © 2025 The TokTok team. | ||
*/ | ||
|
||
#pragma once | ||
|
||
class AbstractScreenshotGrabber; | ||
class QWidget; | ||
|
||
namespace Platform { | ||
/** | ||
* @brief Create a platform-dependent screenshot grabber. | ||
* | ||
* If no platform-specific screenshot grabber is available, this function returns nullptr. | ||
* The caller should then create a default screenshot grabber. | ||
* | ||
* @param parent The parent widget for the screenshot grabber. | ||
*/ | ||
AbstractScreenshotGrabber* createScreenshotGrabber(QWidget* parent); | ||
} // namespace Platform |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
/* SPDX-License-Identifier: GPL-3.0-or-later | ||
* Copyright © 2025 The TokTok team. | ||
*/ | ||
|
||
#include "screenshot_dbus.h" | ||
|
||
#include <QtGlobal> | ||
|
||
#if QT_CONFIG(dbus) | ||
#include <QDBusConnection> | ||
#include <QDBusInterface> | ||
#include <QDBusMessage> | ||
#include <QDebug> | ||
#include <QGuiApplication> | ||
#include <QPixmap> | ||
#include <QRect> | ||
#include <QUrl> | ||
#include <QWidget> | ||
|
||
#include <memory> | ||
|
||
namespace { | ||
const QString PORTAL_DBUS_NAME = QStringLiteral("org.freedesktop.portal.Desktop"); | ||
const QString PORTAL_DBUS_CORE_OBJECT = QStringLiteral("/org/freedesktop/portal/desktop"); | ||
const QString PORTAL_DBUS_REQUEST_INTERFACE = QStringLiteral("org.freedesktop.portal.Request"); | ||
const QString PORTAL_DBUS_SCREENSHOT_INTERFACE = | ||
QStringLiteral("org.freedesktop.portal.Screenshot"); | ||
|
||
QString winIdString(WId winId) | ||
{ | ||
// If we're wayland, return wayland:$id. | ||
if (QGuiApplication::platformName() == QStringLiteral("wayland")) { | ||
return QStringLiteral("wayland:%1").arg(winId); | ||
} | ||
if (QGuiApplication::platformName() == QStringLiteral("xcb")) { | ||
return QStringLiteral("x11:0x%1").arg(winId, 0, 16); | ||
} | ||
qWarning() << "Unknown platform:" << QGuiApplication::platformName() << "cannot take screenshots"; | ||
return {}; | ||
} | ||
} // namespace | ||
|
||
struct DBusScreenshotGrabber::Data | ||
{ | ||
QDBusInterface portal; | ||
WId winId; | ||
QString path; | ||
|
||
explicit Data(QWidget* parent) | ||
: portal(PORTAL_DBUS_NAME, PORTAL_DBUS_CORE_OBJECT, PORTAL_DBUS_SCREENSHOT_INTERFACE, | ||
QDBusConnection::sessionBus(), parent) | ||
, winId{parent->topLevelWidget()->winId()} | ||
{ | ||
} | ||
|
||
QString grabScreen() | ||
{ | ||
if (!portal.connection().isConnected()) { | ||
qWarning() << "DBus connection failed"; | ||
return {}; | ||
} | ||
|
||
if (!portal.isValid()) { | ||
qWarning() << "Failed to connect to org.freedesktop.portal.Screenshot"; | ||
return {}; | ||
} | ||
|
||
const QString wid = winIdString(winId); | ||
if (wid.isEmpty()) { | ||
return {}; | ||
} | ||
|
||
const QDBusMessage reply = portal.call(QStringLiteral("Screenshot"), wid, | ||
QVariantMap{ | ||
{"modal", true}, | ||
{"interactive", true}, | ||
{"handle_token", "1"}, | ||
}); | ||
|
||
if (reply.type() == QDBusMessage::ErrorMessage) { | ||
qWarning() << "Failed to take screenshot:" << reply.errorMessage(); | ||
return {}; | ||
} | ||
|
||
return reply.arguments().value(0).value<QDBusObjectPath>().path(); | ||
} | ||
}; | ||
|
||
DBusScreenshotGrabber::DBusScreenshotGrabber(QWidget* parent) | ||
: AbstractScreenshotGrabber(parent) | ||
, d(std::make_unique<Data>(parent)) | ||
{ | ||
} | ||
|
||
DBusScreenshotGrabber::~DBusScreenshotGrabber() = default; | ||
|
||
DBusScreenshotGrabber* DBusScreenshotGrabber::create(QWidget* parent) | ||
{ | ||
auto grabber = std::make_unique<DBusScreenshotGrabber>(parent); | ||
if (!grabber->isValid()) { | ||
return nullptr; | ||
} | ||
|
||
const QString path = grabber->d->grabScreen(); | ||
if (path.isEmpty()) { | ||
// Some connection problem or unsupported platform. We're not trying this again. | ||
return nullptr; | ||
} | ||
|
||
qDebug() << "Opened screenshot dialog; waiting for response on" << path; | ||
QDBusConnection::sessionBus().connect( | ||
// org.freedesktop.portal.Request::Response | ||
PORTAL_DBUS_NAME, path, PORTAL_DBUS_REQUEST_INTERFACE, QStringLiteral("Response"), | ||
grabber.get(), SLOT(screenshotResponse(uint, QVariantMap))); | ||
|
||
return grabber.release(); | ||
} | ||
|
||
bool DBusScreenshotGrabber::isValid() const | ||
{ | ||
return d->portal.isValid(); | ||
} | ||
|
||
void DBusScreenshotGrabber::showGrabber() | ||
{ | ||
// Nothing to do here. We've done everything in create(). | ||
} | ||
|
||
/** | ||
* https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-signal-org-freedesktop-portal-Request.Response | ||
* | ||
* Emitted when the user interaction for a portal request is over. | ||
* | ||
* The response indicates how the user interaction ended: | ||
* | ||
* 0: Success, the request is carried out | ||
* 1: The user cancelled the interaction | ||
* 2: The user interaction was ended in some other way | ||
*/ | ||
void DBusScreenshotGrabber::screenshotResponse(uint response, QVariantMap results) | ||
{ | ||
switch (response) { | ||
case 0: { | ||
const QUrl uri{results[QStringLiteral("uri")].toString()}; | ||
qDebug() << "Screenshot taken:" << uri.toString(); | ||
emit screenshotTaken(QPixmap(uri.toLocalFile())); | ||
break; | ||
} | ||
case 1: | ||
qDebug() << "User cancelled screenshot request"; | ||
emit rejected(); | ||
break; | ||
default: | ||
qWarning() << "Screenshot request failed:" << response; | ||
emit rejected(); | ||
break; | ||
} | ||
|
||
// We're done, clean up. | ||
deleteLater(); | ||
} | ||
#endif // QT_CONFIG(dbus) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* SPDX-License-Identifier: GPL-3.0-or-later | ||
* Copyright © 2025 The TokTok team. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include "src/widget/tool/abstractscreenshotgrabber.h" | ||
|
||
#include <QVariantMap> | ||
|
||
#include <memory> | ||
|
||
#if QT_CONFIG(dbus) | ||
/** @brief Grabs screenshot using org.freedesktop.portal.Screenshot. | ||
* | ||
* https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-org.freedesktop.portal.Screenshot | ||
*/ | ||
class DBusScreenshotGrabber : public AbstractScreenshotGrabber | ||
{ | ||
Q_OBJECT | ||
|
||
public: | ||
// Don't use these directly, use create() instead. | ||
explicit DBusScreenshotGrabber(QWidget* parent); | ||
~DBusScreenshotGrabber() override; | ||
|
||
// Create a screenshot grabber. Returns nullptr if no DBus connection could be established. | ||
static DBusScreenshotGrabber* create(QWidget* parent); | ||
|
||
bool isValid() const; | ||
void showGrabber() override; | ||
|
||
private slots: | ||
void screenshotResponse(uint response, QVariantMap results); | ||
|
||
private: | ||
struct Data; | ||
std::unique_ptr<Data> d; | ||
}; | ||
#endif // QT_CONFIG(dbus) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/* SPDX-License-Identifier: GPL-3.0-or-later | ||
* Copyright © 2015-2019 by The qTox Project Contributors | ||
* Copyright © 2024-2025 The TokTok team. | ||
*/ | ||
|
||
#include "abstractscreenshotgrabber.h" | ||
|
||
AbstractScreenshotGrabber::AbstractScreenshotGrabber(QObject* parent) | ||
: QObject(parent) | ||
{ | ||
} | ||
|
||
AbstractScreenshotGrabber::~AbstractScreenshotGrabber() = default; |
Oops, something went wrong.