6161#undef FocusOut
6262// #undef those Xlib #defines that conflict with QEvent::Type enum
6363#include " qt/utils.h"
64+ #include < QDBusConnection>
65+ #include < QDBusError>
66+ #include < QDBusInterface>
67+ #include < QDBusObjectPath>
68+ #include < QDBusPendingReply>
69+ #include < QDBusVariant>
70+ #include < QEventLoop>
71+ #include < QTimer>
6472#endif
6573
6674#include " QR-Code-scanner/Decoder.h"
7078namespace
7179{
7280
73- QPixmap screenshot ()
81+ std::unordered_set<QWindow *> hideVisibleWindows ()
7482{
7583 std::unordered_set<QWindow *> hidden;
7684 const QWindowList windows = QGuiApplication::allWindows ();
@@ -82,16 +90,43 @@ QPixmap screenshot()
8290 window->hide ();
8391 }
8492 }
85- const auto unhide = sg::make_scope_guard ([&hidden]() {
86- for (QWindow *window : hidden)
87- {
88- window->show ();
89- }
90- });
93+ return hidden;
94+ }
9195
92- return QGuiApplication::primaryScreen ()->grabWindow (0 );
96+ void showWindows (const std::unordered_set<QWindow *> &windows)
97+ {
98+ for (QWindow *window : windows)
99+ {
100+ window->show ();
101+ }
93102}
94103
104+ #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
105+ bool isWayland ()
106+ {
107+ if (QGuiApplication::platformName ().contains (" wayland" , Qt::CaseInsensitive))
108+ {
109+ return true ;
110+ }
111+
112+ if (!qEnvironmentVariableIsEmpty (" WAYLAND_DISPLAY" ))
113+ {
114+ return true ;
115+ }
116+
117+ return QString::fromLocal8Bit (qgetenv (" XDG_SESSION_TYPE" )).compare (QStringLiteral (" wayland" ), Qt::CaseInsensitive) == 0 ;
118+ }
119+
120+ QVariant unwrapDbusVariant (const QVariant &value)
121+ {
122+ if (value.canConvert <QDBusVariant>())
123+ {
124+ return value.value <QDBusVariant>().variant ();
125+ }
126+ return value;
127+ }
128+ #endif
129+
95130} // namespace
96131
97132#if defined(Q_OS_WIN)
@@ -126,6 +161,129 @@ OSHelper::OSHelper(QObject *parent) : QObject(parent)
126161
127162}
128163
164+ #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
165+ void OSHelper::resetScreenshotPortalResponse ()
166+ {
167+ m_screenshotPortalResponseReceived = false ;
168+ m_screenshotPortalResponse = 1 ;
169+ m_screenshotPortalResults.clear ();
170+ }
171+
172+ void OSHelper::handleScreenshotPortalResponse (uint responseCode, const QVariantMap &responseResults)
173+ {
174+ m_screenshotPortalResponseReceived = true ;
175+ m_screenshotPortalResponse = responseCode;
176+ m_screenshotPortalResults = responseResults;
177+ emit screenshotPortalResponseReceived ();
178+ }
179+
180+ QImage OSHelper::screenshotPortal ()
181+ {
182+ QDBusInterface portal (
183+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
184+ QStringLiteral (" /org/freedesktop/portal/desktop" ),
185+ QStringLiteral (" org.freedesktop.portal.Screenshot" ),
186+ QDBusConnection::sessionBus ());
187+ if (!portal.isValid ())
188+ {
189+ qWarning () << " XDG screenshot portal is unavailable:" << QDBusConnection::sessionBus ().lastError ().message ();
190+ return QImage ();
191+ }
192+
193+ QVariantMap options;
194+ options.insert (QStringLiteral (" interactive" ), true );
195+ options.insert (QStringLiteral (" modal" ), true );
196+
197+ QDBusPendingReply<QDBusObjectPath> reply = portal.asyncCallWithArgumentList (
198+ QStringLiteral (" Screenshot" ),
199+ QVariantList{QString (), options});
200+ reply.waitForFinished ();
201+ if (reply.isError ())
202+ {
203+ qWarning () << " XDG screenshot portal request failed:" << reply.error ().message ();
204+ return QImage ();
205+ }
206+
207+ resetScreenshotPortalResponse ();
208+
209+ const QDBusObjectPath requestPath = reply.value ();
210+ if (!QDBusConnection::sessionBus ().connect (
211+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
212+ requestPath.path (),
213+ QStringLiteral (" org.freedesktop.portal.Request" ),
214+ QStringLiteral (" Response" ),
215+ this ,
216+ SLOT (handleScreenshotPortalResponse (uint,QVariantMap))))
217+ {
218+ qWarning () << " Failed to connect to XDG screenshot portal response" ;
219+ return QImage ();
220+ }
221+
222+ QEventLoop loop;
223+ QTimer timer;
224+ timer.setSingleShot (true );
225+ QObject::connect (this , SIGNAL (screenshotPortalResponseReceived ()), &loop, SLOT (quit ()));
226+ QObject::connect (&timer, SIGNAL (timeout ()), &loop, SLOT (quit ()));
227+ timer.start (60000 );
228+
229+ if (!m_screenshotPortalResponseReceived)
230+ {
231+ loop.exec ();
232+ }
233+
234+ QDBusConnection::sessionBus ().disconnect (
235+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
236+ requestPath.path (),
237+ QStringLiteral (" org.freedesktop.portal.Request" ),
238+ QStringLiteral (" Response" ),
239+ this ,
240+ SLOT (handleScreenshotPortalResponse (uint,QVariantMap)));
241+
242+ if (!m_screenshotPortalResponseReceived)
243+ {
244+ qWarning () << " Timed out waiting for XDG screenshot portal response" ;
245+ return QImage ();
246+ }
247+ if (m_screenshotPortalResponse != 0 )
248+ {
249+ qWarning () << " XDG screenshot portal request was not approved:" << m_screenshotPortalResponse;
250+ return QImage ();
251+ }
252+
253+ const QVariant uriValue = unwrapDbusVariant (m_screenshotPortalResults.value (QStringLiteral (" uri" )));
254+ const QUrl uri (uriValue.toString ());
255+ if (!uri.isLocalFile ())
256+ {
257+ qWarning () << " XDG screenshot portal returned an unsupported URI:" << uri;
258+ return QImage ();
259+ }
260+
261+ QImage image (uri.toLocalFile ());
262+ if (image.isNull ())
263+ {
264+ qWarning () << " Failed to load XDG screenshot portal image:" << uri;
265+ }
266+ return image;
267+ }
268+ #endif
269+
270+ QImage OSHelper::screenshot ()
271+ {
272+ const std::unordered_set<QWindow *> hidden = hideVisibleWindows ();
273+ const auto unhide = sg::make_scope_guard ([&hidden]() {
274+ showWindows (hidden);
275+ });
276+
277+ #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
278+ if (isWayland ())
279+ {
280+ return screenshotPortal ();
281+ }
282+ #endif
283+
284+ return QGuiApplication::primaryScreen ()->grabWindow (0 ).toImage ();
285+ }
286+
129287void OSHelper::createDesktopEntry () const
130288{
131289#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
@@ -138,13 +296,18 @@ QString OSHelper::downloadLocation() const
138296 return QStandardPaths::writableLocation (QStandardPaths::DownloadLocation);
139297}
140298
141- QList<QString> OSHelper::grabQrCodesFromScreen () const
299+ QList<QString> OSHelper::grabQrCodesFromScreen ()
142300{
143301 QList<QString> codes;
144302
145303 try
146304 {
147- const QImage image = screenshot ().toImage ();
305+ const QImage image = screenshot ();
306+ if (image.isNull ())
307+ {
308+ return codes;
309+ }
310+
148311 const std::vector<std::string> decoded = QrDecoder ().decode (image);
149312 std::for_each (decoded.begin (), decoded.end (), [&codes](const std::string &code) {
150313 codes.push_back (QString::fromStdString (code));
0 commit comments