Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ set(QT5_LIBRARIES
Qt5Xml
)

if(UNIX AND NOT APPLE AND NOT ANDROID)
list(APPEND QT5_LIBRARIES Qt5DBus)
endif()

if(WITH_SCANNER)
list(APPEND QT5_LIBRARIES Qt5Multimedia)
endif()
Expand Down Expand Up @@ -300,6 +304,11 @@ if(STATIC)
pkg_check_modules(FONTCONFIG REQUIRED fontconfig)
list(APPEND QT5_LIBRARIES ${FONTCONFIG_STATIC_LIBRARIES})
endif()

if(UNIX AND NOT APPLE AND NOT ANDROID)
pkg_check_modules(DBUS1_PKG_CONFIG REQUIRED dbus-1)
list(APPEND QT5_LIBRARIES ${DBUS1_PKG_CONFIG_STATIC_LIBRARIES} ${DBUS1_PKG_CONFIG_STATIC_LDFLAGS_OTHER})
endif()
endif()

if(ANDROID)
Expand Down
13 changes: 11 additions & 2 deletions Dockerfile.linux
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@ ENV CXXFLAGS="-fPIC"
ENV SOURCE_DATE_EPOCH=1397818193

RUN apt update && \
apt install -y automake autopoint bison gettext git gperf libgl1-mesa-dev libglib2.0-dev ninja-build \
apt install -y autoconf-archive automake autopoint bison gettext git gperf libexpat1-dev libgl1-mesa-dev libglib2.0-dev ninja-build \
libpng-dev libpthread-stubs0-dev libsodium-dev libtool-bin libudev-dev libusb-1.0-0-dev mesa-common-dev \
pkg-config python wget xutils-dev

RUN git clone -b dbus-1.12.2 --depth 1 https://gitlab.freedesktop.org/dbus/dbus.git && \
cd dbus && \
git reset --hard 0f0968336b9711349023e1d41f075b2bccf7c20b && \
./autogen.sh && \
./configure --disable-shared --enable-static --disable-systemd --disable-launchd --disable-selinux --disable-apparmor --disable-libaudit --disable-xml-docs --disable-doxygen-docs --disable-ducktype-docs --disable-modular-tests --disable-embedded-tests --disable-x11-autolaunch --without-x --with-systemdsystemunitdir=no --with-systemduserunitdir=no && \
make -j$THREADS && \
make -j$THREADS install && \
rm -rf $(pwd)

RUN git clone -b xorgproto-2020.1 --depth 1 https://gitlab.freedesktop.org/xorg/proto/xorgproto && \
cd xorgproto && \
git reset --hard c62e8203402cafafa5ba0357b6d1c019156c9f36 && \
Expand Down Expand Up @@ -198,7 +207,7 @@ RUN rm /usr/lib/x86_64-linux-gnu/libX11.a && \
sed -i s/\\/usr\\/X11R6\\/lib64/\\/usr\\/local\\/lib/ qtbase/mkspecs/linux-g++-64/qmake.conf && \
./configure --prefix=/usr -platform linux-g++-64 -opensource -confirm-license -release -static -no-avx \
-opengl desktop -qpa xcb -xcb -xcb-xlib -feature-xlib -system-freetype -fontconfig -glib \
-no-dbus -no-feature-qml-worker-script -no-linuxfb -no-openssl -no-sql-sqlite -no-kms -no-use-gold-linker \
-no-feature-qml-worker-script -no-linuxfb -no-openssl -no-sql-sqlite -no-kms -no-use-gold-linker \
-qt-harfbuzz -qt-libjpeg -qt-libpng -qt-pcre -qt-zlib \
-skip qt3d -skip qtandroidextras -skip qtcanvas3d -skip qtcharts -skip qtconnectivity -skip qtdatavis3d \
-skip qtdoc -skip qtgamepad -skip qtlocation -skip qtmacextras -skip qtnetworkauth -skip qtpurchasing \
Expand Down
240 changes: 230 additions & 10 deletions src/main/oshelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@
#endif
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include "qt/utils.h"
#include <QDBusConnection>
#include <QDBusError>
#include <QDBusInterface>
#include <QDBusObjectPath>
#include <QDBusPendingReply>
#include <QDBusVariant>
#include <QEventLoop>
#include <QTimer>
#endif

#include "QR-Code-scanner/Decoder.h"
Expand All @@ -63,7 +71,7 @@
namespace
{

QPixmap screenshot()
std::unordered_set<QWindow *> hideVisibleWindows()
{
std::unordered_set<QWindow *> hidden;
const QWindowList windows = QGuiApplication::allWindows();
Expand All @@ -75,16 +83,43 @@ QPixmap screenshot()
window->hide();
}
}
const auto unhide = sg::make_scope_guard([&hidden]() {
for (QWindow *window : hidden)
{
window->show();
}
});
return hidden;
}

void showWindows(const std::unordered_set<QWindow *> &windows)
{
for (QWindow *window : windows)
{
window->show();
}
}

#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
bool isWayland()
{
if (QGuiApplication::platformName().contains("wayland", Qt::CaseInsensitive))
{
return true;
}

if (!qEnvironmentVariableIsEmpty("WAYLAND_DISPLAY"))
{
return true;
}

return QGuiApplication::primaryScreen()->grabWindow(0);
return QString::fromLocal8Bit(qgetenv("XDG_SESSION_TYPE")).compare(QStringLiteral("wayland"), Qt::CaseInsensitive) == 0;
}

QVariant unwrapDbusVariant(const QVariant &value)
{
if (value.canConvert<QDBusVariant>())
{
return value.value<QDBusVariant>().variant();
}
return value;
}
#endif

} // namespace

#if defined(Q_OS_WIN)
Expand Down Expand Up @@ -119,6 +154,186 @@ OSHelper::OSHelper(QObject *parent) : QObject(parent)

}

#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
void OSHelper::resetScreenshotPortalResponse()
{
m_screenshotPortalResponseReceived = false;
m_screenshotPortalResponse = 1;
m_screenshotPortalResults.clear();
}

void OSHelper::handleScreenshotPortalResponse(uint responseCode, const QVariantMap &responseResults)
{
m_screenshotPortalResponseReceived = true;
m_screenshotPortalResponse = responseCode;
m_screenshotPortalResults = responseResults;
emit screenshotPortalResponseReceived();
}

QImage OSHelper::screenshotPortal()
{
if (m_screenshotPortalInProgress)
{
qWarning() << "XDG screenshot portal request is already in progress";
return QImage();
}

m_screenshotPortalInProgress = true;
const auto resetInProgress = sg::make_scope_guard([this]() {
m_screenshotPortalInProgress = false;
});

QDBusConnection sessionBus = QDBusConnection::sessionBus();
QDBusInterface portal(
QStringLiteral("org.freedesktop.portal.Desktop"),
QStringLiteral("/org/freedesktop/portal/desktop"),
QStringLiteral("org.freedesktop.portal.Screenshot"),
sessionBus);
if (!portal.isValid())
{
qWarning() << "XDG screenshot portal is unavailable:" << sessionBus.lastError().message();
return QImage();
}

resetScreenshotPortalResponse();

const QString handleToken = QStringLiteral("monero_gui_%1_%2")
.arg(QRandomGenerator::global()->generate(), 8, 16, QLatin1Char('0'))
.arg(QRandomGenerator::global()->generate(), 8, 16, QLatin1Char('0'));
QString sender = sessionBus.baseService();
if (sender.startsWith(QLatin1Char(':')))
{
sender.remove(0, 1);
}
sender.replace(QLatin1Char('.'), QLatin1Char('_'));
if (sender.isEmpty())
{
qWarning() << "XDG screenshot portal cannot determine DBus sender name";
return QImage();
}

QString requestPath = QStringLiteral("/org/freedesktop/portal/desktop/request/%1/%2").arg(sender, handleToken);
if (!sessionBus.connect(
QStringLiteral("org.freedesktop.portal.Desktop"),
requestPath,
QStringLiteral("org.freedesktop.portal.Request"),
QStringLiteral("Response"),
this,
SLOT(handleScreenshotPortalResponse(uint,QVariantMap))))
{
qWarning() << "Failed to connect to XDG screenshot portal response";
return QImage();
}

const auto disconnectRequest = sg::make_scope_guard([this, sessionBus, &requestPath]() mutable {
sessionBus.disconnect(
QStringLiteral("org.freedesktop.portal.Desktop"),
requestPath,
QStringLiteral("org.freedesktop.portal.Request"),
QStringLiteral("Response"),
this,
SLOT(handleScreenshotPortalResponse(uint,QVariantMap)));
});

QVariantMap options;
options.insert(QStringLiteral("interactive"), true);
options.insert(QStringLiteral("modal"), true);
options.insert(QStringLiteral("handle_token"), handleToken);

QDBusPendingReply<QDBusObjectPath> reply = portal.asyncCallWithArgumentList(
QStringLiteral("Screenshot"),
QVariantList{QString(), options});
reply.waitForFinished();
if (reply.isError())
{
qWarning() << "XDG screenshot portal request failed:" << reply.error().message();
return QImage();
}

const QString returnedRequestPath = reply.value().path();
if (returnedRequestPath != requestPath)
{
sessionBus.disconnect(
QStringLiteral("org.freedesktop.portal.Desktop"),
requestPath,
QStringLiteral("org.freedesktop.portal.Request"),
QStringLiteral("Response"),
this,
SLOT(handleScreenshotPortalResponse(uint,QVariantMap)));

requestPath = returnedRequestPath;
if (!sessionBus.connect(
QStringLiteral("org.freedesktop.portal.Desktop"),
requestPath,
QStringLiteral("org.freedesktop.portal.Request"),
QStringLiteral("Response"),
this,
SLOT(handleScreenshotPortalResponse(uint,QVariantMap))))
{
qWarning() << "Failed to connect to XDG screenshot portal response";
return QImage();
}
}

QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
QObject::connect(this, SIGNAL(screenshotPortalResponseReceived()), &loop, SLOT(quit()));
QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
timer.start(60000);

if (!m_screenshotPortalResponseReceived)
{
loop.exec();
}

if (!m_screenshotPortalResponseReceived)
{
qWarning() << "Timed out waiting for XDG screenshot portal response";
return QImage();
}
if (m_screenshotPortalResponse != 0)
{
qWarning() << "XDG screenshot portal request was not approved:" << m_screenshotPortalResponse;
return QImage();
}

const QVariant uriValue = unwrapDbusVariant(m_screenshotPortalResults.value(QStringLiteral("uri")));
const QUrl uri(uriValue.toString());
if (!uri.isLocalFile())
{
qWarning() << "XDG screenshot portal returned an unsupported URI:" << uri;
return QImage();
}

const QString imagePath = uri.toLocalFile();
QImage image(imagePath);
QFile::remove(imagePath);
if (image.isNull())
{
qWarning() << "Failed to load XDG screenshot portal image:" << uri;
}
return image;
}
#endif

QImage OSHelper::screenshot()
{
const std::unordered_set<QWindow *> hidden = hideVisibleWindows();
const auto unhide = sg::make_scope_guard([&hidden]() {
showWindows(hidden);
});

#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
if (isWayland())
{
return screenshotPortal();
}
#endif

return QGuiApplication::primaryScreen()->grabWindow(0).toImage();
}

void OSHelper::createDesktopEntry() const
{
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
Expand All @@ -131,13 +346,18 @@ QString OSHelper::downloadLocation() const
return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
}

QList<QString> OSHelper::grabQrCodesFromScreen() const
QList<QString> OSHelper::grabQrCodesFromScreen()
{
QList<QString> codes;

try
{
const QImage image = screenshot().toImage();
const QImage image = screenshot();
if (image.isNull())
{
return codes;
}

const std::vector<std::string> decoded = QrDecoder().decode(image);
std::for_each(decoded.begin(), decoded.end(), [&codes](const std::string &code) {
codes.push_back(QString::fromStdString(code));
Expand Down
24 changes: 22 additions & 2 deletions src/main/oshelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@
#define OSHELPER_H

#include <QList>
#include <QImage>
#include <QObject>
#include <QString>
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include <QVariantMap>
#endif
/**
* @brief The OSHelper class - exports to QML some OS-related functions
*/
Expand All @@ -45,7 +49,7 @@ class OSHelper : public QObject

Q_INVOKABLE void createDesktopEntry() const;
Q_INVOKABLE QString downloadLocation() const;
Q_INVOKABLE QList<QString> grabQrCodesFromScreen() const;
Q_INVOKABLE QList<QString> grabQrCodesFromScreen();
Q_INVOKABLE bool openFile(const QString &filePath) const;
Q_INVOKABLE bool openContainingFolder(const QString &filePath) const;
Q_INVOKABLE QString openSaveFileDialog(const QString &title, const QString &folder, const QString &filename) const;
Expand All @@ -61,10 +65,26 @@ class OSHelper : public QObject

static std::pair<quint8, QString> getNetworkTypeAndAddressFromFile(const QString &wallet);
private:
QImage screenshot();
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
QImage screenshotPortal();
void resetScreenshotPortalResponse();

bool m_screenshotPortalResponseReceived = false;
uint m_screenshotPortalResponse = 1;
QVariantMap m_screenshotPortalResults;
bool m_screenshotPortalInProgress = false;
#endif

signals:
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
void screenshotPortalResponseReceived();
#endif

public slots:
private slots:
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
void handleScreenshotPortalResponse(uint responseCode, const QVariantMap &responseResults);
#endif
};

#endif // OSHELPER_H
Loading