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+ }
95+
96+ void showWindows (const std::unordered_set<QWindow *> &windows)
97+ {
98+ for (QWindow *window : windows)
99+ {
100+ window->show ();
101+ }
102+ }
103+
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+ }
91116
92- return QGuiApplication::primaryScreen ()-> grabWindow ( 0 ) ;
117+ return QString::fromLocal8Bit ( qgetenv ( " XDG_SESSION_TYPE " )). compare ( QStringLiteral ( " wayland " ), Qt::CaseInsensitive) == 0 ;
93118}
94119
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,186 @@ 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+ if (m_screenshotPortalInProgress)
183+ {
184+ qWarning () << " XDG screenshot portal request is already in progress" ;
185+ return QImage ();
186+ }
187+
188+ m_screenshotPortalInProgress = true ;
189+ const auto resetInProgress = sg::make_scope_guard ([this ]() {
190+ m_screenshotPortalInProgress = false ;
191+ });
192+
193+ QDBusConnection sessionBus = QDBusConnection::sessionBus ();
194+ QDBusInterface portal (
195+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
196+ QStringLiteral (" /org/freedesktop/portal/desktop" ),
197+ QStringLiteral (" org.freedesktop.portal.Screenshot" ),
198+ sessionBus);
199+ if (!portal.isValid ())
200+ {
201+ qWarning () << " XDG screenshot portal is unavailable:" << sessionBus.lastError ().message ();
202+ return QImage ();
203+ }
204+
205+ resetScreenshotPortalResponse ();
206+
207+ const QString handleToken = QStringLiteral (" monero_gui_%1_%2" )
208+ .arg (QRandomGenerator::global ()->generate (), 8 , 16 , QLatin1Char (' 0' ))
209+ .arg (QRandomGenerator::global ()->generate (), 8 , 16 , QLatin1Char (' 0' ));
210+ QString sender = sessionBus.baseService ();
211+ if (sender.startsWith (QLatin1Char (' :' )))
212+ {
213+ sender.remove (0 , 1 );
214+ }
215+ sender.replace (QLatin1Char (' .' ), QLatin1Char (' _' ));
216+ if (sender.isEmpty ())
217+ {
218+ qWarning () << " XDG screenshot portal cannot determine DBus sender name" ;
219+ return QImage ();
220+ }
221+
222+ QString requestPath = QStringLiteral (" /org/freedesktop/portal/desktop/request/%1/%2" ).arg (sender, handleToken);
223+ if (!sessionBus.connect (
224+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
225+ requestPath,
226+ QStringLiteral (" org.freedesktop.portal.Request" ),
227+ QStringLiteral (" Response" ),
228+ this ,
229+ SLOT (handleScreenshotPortalResponse (uint,QVariantMap))))
230+ {
231+ qWarning () << " Failed to connect to XDG screenshot portal response" ;
232+ return QImage ();
233+ }
234+
235+ const auto disconnectRequest = sg::make_scope_guard ([this , sessionBus, &requestPath]() mutable {
236+ sessionBus.disconnect (
237+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
238+ requestPath,
239+ QStringLiteral (" org.freedesktop.portal.Request" ),
240+ QStringLiteral (" Response" ),
241+ this ,
242+ SLOT (handleScreenshotPortalResponse (uint,QVariantMap)));
243+ });
244+
245+ QVariantMap options;
246+ options.insert (QStringLiteral (" interactive" ), true );
247+ options.insert (QStringLiteral (" modal" ), true );
248+ options.insert (QStringLiteral (" handle_token" ), handleToken);
249+
250+ QDBusPendingReply<QDBusObjectPath> reply = portal.asyncCallWithArgumentList (
251+ QStringLiteral (" Screenshot" ),
252+ QVariantList{QString (), options});
253+ reply.waitForFinished ();
254+ if (reply.isError ())
255+ {
256+ qWarning () << " XDG screenshot portal request failed:" << reply.error ().message ();
257+ return QImage ();
258+ }
259+
260+ const QString returnedRequestPath = reply.value ().path ();
261+ if (returnedRequestPath != requestPath)
262+ {
263+ sessionBus.disconnect (
264+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
265+ requestPath,
266+ QStringLiteral (" org.freedesktop.portal.Request" ),
267+ QStringLiteral (" Response" ),
268+ this ,
269+ SLOT (handleScreenshotPortalResponse (uint,QVariantMap)));
270+
271+ requestPath = returnedRequestPath;
272+ if (!sessionBus.connect (
273+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
274+ requestPath,
275+ QStringLiteral (" org.freedesktop.portal.Request" ),
276+ QStringLiteral (" Response" ),
277+ this ,
278+ SLOT (handleScreenshotPortalResponse (uint,QVariantMap))))
279+ {
280+ qWarning () << " Failed to connect to XDG screenshot portal response" ;
281+ return QImage ();
282+ }
283+ }
284+
285+ QEventLoop loop;
286+ QTimer timer;
287+ timer.setSingleShot (true );
288+ QObject::connect (this , SIGNAL (screenshotPortalResponseReceived ()), &loop, SLOT (quit ()));
289+ QObject::connect (&timer, SIGNAL (timeout ()), &loop, SLOT (quit ()));
290+ timer.start (60000 );
291+
292+ if (!m_screenshotPortalResponseReceived)
293+ {
294+ loop.exec ();
295+ }
296+
297+ if (!m_screenshotPortalResponseReceived)
298+ {
299+ qWarning () << " Timed out waiting for XDG screenshot portal response" ;
300+ return QImage ();
301+ }
302+ if (m_screenshotPortalResponse != 0 )
303+ {
304+ qWarning () << " XDG screenshot portal request was not approved:" << m_screenshotPortalResponse;
305+ return QImage ();
306+ }
307+
308+ const QVariant uriValue = unwrapDbusVariant (m_screenshotPortalResults.value (QStringLiteral (" uri" )));
309+ const QUrl uri (uriValue.toString ());
310+ if (!uri.isLocalFile ())
311+ {
312+ qWarning () << " XDG screenshot portal returned an unsupported URI:" << uri;
313+ return QImage ();
314+ }
315+
316+ const QString imagePath = uri.toLocalFile ();
317+ QImage image (imagePath);
318+ QFile::remove (imagePath);
319+ if (image.isNull ())
320+ {
321+ qWarning () << " Failed to load XDG screenshot portal image:" << uri;
322+ }
323+ return image;
324+ }
325+ #endif
326+
327+ QImage OSHelper::screenshot ()
328+ {
329+ const std::unordered_set<QWindow *> hidden = hideVisibleWindows ();
330+ const auto unhide = sg::make_scope_guard ([&hidden]() {
331+ showWindows (hidden);
332+ });
333+
334+ #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
335+ if (isWayland ())
336+ {
337+ return screenshotPortal ();
338+ }
339+ #endif
340+
341+ return QGuiApplication::primaryScreen ()->grabWindow (0 ).toImage ();
342+ }
343+
129344void OSHelper::createDesktopEntry () const
130345{
131346#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
@@ -138,13 +353,18 @@ QString OSHelper::downloadLocation() const
138353 return QStandardPaths::writableLocation (QStandardPaths::DownloadLocation);
139354}
140355
141- QList<QString> OSHelper::grabQrCodesFromScreen () const
356+ QList<QString> OSHelper::grabQrCodesFromScreen ()
142357{
143358 QList<QString> codes;
144359
145360 try
146361 {
147- const QImage image = screenshot ().toImage ();
362+ const QImage image = screenshot ();
363+ if (image.isNull ())
364+ {
365+ return codes;
366+ }
367+
148368 const std::vector<std::string> decoded = QrDecoder ().decode (image);
149369 std::for_each (decoded.begin (), decoded.end (), [&codes](const std::string &code) {
150370 codes.push_back (QString::fromStdString (code));
0 commit comments