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,142 @@ 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+ QDBusInterface portal (
194+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
195+ QStringLiteral (" /org/freedesktop/portal/desktop" ),
196+ QStringLiteral (" org.freedesktop.portal.Screenshot" ),
197+ QDBusConnection::sessionBus ());
198+ if (!portal.isValid ())
199+ {
200+ qWarning () << " XDG screenshot portal is unavailable:" << QDBusConnection::sessionBus ().lastError ().message ();
201+ return QImage ();
202+ }
203+
204+ QVariantMap options;
205+ options.insert (QStringLiteral (" interactive" ), true );
206+ options.insert (QStringLiteral (" modal" ), true );
207+
208+ QDBusPendingReply<QDBusObjectPath> reply = portal.asyncCallWithArgumentList (
209+ QStringLiteral (" Screenshot" ),
210+ QVariantList{QString (), options});
211+ reply.waitForFinished ();
212+ if (reply.isError ())
213+ {
214+ qWarning () << " XDG screenshot portal request failed:" << reply.error ().message ();
215+ return QImage ();
216+ }
217+
218+ resetScreenshotPortalResponse ();
219+
220+ const QDBusObjectPath requestPath = reply.value ();
221+ if (!QDBusConnection::sessionBus ().connect (
222+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
223+ requestPath.path (),
224+ QStringLiteral (" org.freedesktop.portal.Request" ),
225+ QStringLiteral (" Response" ),
226+ this ,
227+ SLOT (handleScreenshotPortalResponse (uint,QVariantMap))))
228+ {
229+ qWarning () << " Failed to connect to XDG screenshot portal response" ;
230+ return QImage ();
231+ }
232+
233+ QEventLoop loop;
234+ QTimer timer;
235+ timer.setSingleShot (true );
236+ QObject::connect (this , SIGNAL (screenshotPortalResponseReceived ()), &loop, SLOT (quit ()));
237+ QObject::connect (&timer, SIGNAL (timeout ()), &loop, SLOT (quit ()));
238+ timer.start (60000 );
239+
240+ if (!m_screenshotPortalResponseReceived)
241+ {
242+ loop.exec ();
243+ }
244+
245+ QDBusConnection::sessionBus ().disconnect (
246+ QStringLiteral (" org.freedesktop.portal.Desktop" ),
247+ requestPath.path (),
248+ QStringLiteral (" org.freedesktop.portal.Request" ),
249+ QStringLiteral (" Response" ),
250+ this ,
251+ SLOT (handleScreenshotPortalResponse (uint,QVariantMap)));
252+
253+ if (!m_screenshotPortalResponseReceived)
254+ {
255+ qWarning () << " Timed out waiting for XDG screenshot portal response" ;
256+ return QImage ();
257+ }
258+ if (m_screenshotPortalResponse != 0 )
259+ {
260+ qWarning () << " XDG screenshot portal request was not approved:" << m_screenshotPortalResponse;
261+ return QImage ();
262+ }
263+
264+ const QVariant uriValue = unwrapDbusVariant (m_screenshotPortalResults.value (QStringLiteral (" uri" )));
265+ const QUrl uri (uriValue.toString ());
266+ if (!uri.isLocalFile ())
267+ {
268+ qWarning () << " XDG screenshot portal returned an unsupported URI:" << uri;
269+ return QImage ();
270+ }
271+
272+ const QString imagePath = uri.toLocalFile ();
273+ QImage image (imagePath);
274+ QFile::remove (imagePath);
275+ if (image.isNull ())
276+ {
277+ qWarning () << " Failed to load XDG screenshot portal image:" << uri;
278+ }
279+ return image;
280+ }
281+ #endif
282+
283+ QImage OSHelper::screenshot ()
284+ {
285+ const std::unordered_set<QWindow *> hidden = hideVisibleWindows ();
286+ const auto unhide = sg::make_scope_guard ([&hidden]() {
287+ showWindows (hidden);
288+ });
289+
290+ #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
291+ if (isWayland ())
292+ {
293+ return screenshotPortal ();
294+ }
295+ #endif
296+
297+ return QGuiApplication::primaryScreen ()->grabWindow (0 ).toImage ();
298+ }
299+
129300void OSHelper::createDesktopEntry () const
130301{
131302#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
@@ -138,13 +309,18 @@ QString OSHelper::downloadLocation() const
138309 return QStandardPaths::writableLocation (QStandardPaths::DownloadLocation);
139310}
140311
141- QList<QString> OSHelper::grabQrCodesFromScreen () const
312+ QList<QString> OSHelper::grabQrCodesFromScreen ()
142313{
143314 QList<QString> codes;
144315
145316 try
146317 {
147- const QImage image = screenshot ().toImage ();
318+ const QImage image = screenshot ();
319+ if (image.isNull ())
320+ {
321+ return codes;
322+ }
323+
148324 const std::vector<std::string> decoded = QrDecoder ().decode (image);
149325 std::for_each (decoded.begin (), decoded.end (), [&codes](const std::string &code) {
150326 codes.push_back (QString::fromStdString (code));
0 commit comments