From 594fc3e665cff73d12fe9d576ff15b5393ca343e Mon Sep 17 00:00:00 2001 From: Gres Date: Fri, 23 Apr 2021 21:02:40 +0300 Subject: [PATCH] Add wayland support --- screen-translator.pro | 4 +- src/capture/capturer.cpp | 25 +++-- src/capture/capturer.h | 3 + src/capture/waylandcapturer.cpp | 184 ++++++++++++++++++++++++++++++++ src/capture/waylandcapturer.h | 32 ++++++ 5 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 src/capture/waylandcapturer.cpp create mode 100644 src/capture/waylandcapturer.h diff --git a/screen-translator.pro b/screen-translator.pro index aab4d11..448927a 100644 --- a/screen-translator.pro +++ b/screen-translator.pro @@ -14,7 +14,7 @@ win32{ LIBS += -lUser32 } linux{ - QT += x11extras + QT += x11extras dbus LIBS += -lX11 } @@ -37,6 +37,7 @@ HEADERS += \ src/capture/captureareaeditor.h \ src/capture/captureareaselector.h \ src/capture/capturer.h \ + src/capture/waylandcapturer.h \ src/commonmodels.h \ src/correct/corrector.h \ src/correct/correctorworker.h \ @@ -73,6 +74,7 @@ SOURCES += \ src/capture/captureareaeditor.cpp \ src/capture/captureareaselector.cpp \ src/capture/capturer.cpp \ + src/capture/waylandcapturer.cpp \ src/commonmodels.cpp \ src/correct/corrector.cpp \ src/correct/correctorworker.cpp \ diff --git a/src/capture/capturer.cpp b/src/capture/capturer.cpp index 51b0b26..08a2c58 100644 --- a/src/capture/capturer.cpp +++ b/src/capture/capturer.cpp @@ -4,7 +4,7 @@ #include "debug.h" #include "manager.h" #include "settings.h" -#include "task.h" +#include "waylandcapturer.h" #include #include @@ -17,6 +17,10 @@ Capturer::Capturer(Manager &manager, const Settings &settings, , selector_(std::make_unique(*this, settings_, models, pixmap_)) { +#ifdef Q_OS_LINUX + if (WaylandCapturer::isWayland()) + wayland_ = std::make_unique(); +#endif } Capturer::~Capturer() = default; @@ -57,11 +61,20 @@ void Capturer::updatePixmap() QPixmap combined(rect.size()); QPainter p(&combined); - for (const auto screen : screens) { - const auto geometry = screen->geometry(); - const auto pixmap = - screen->grabWindow(0, 0, 0, geometry.width(), geometry.height()); - p.drawPixmap(geometry, pixmap); + if (!wayland_) { + for (const auto screen : screens) { + const auto geometry = screen->geometry(); + const auto pixmap = + screen->grabWindow(0, 0, 0, geometry.width(), geometry.height()); + p.drawPixmap(geometry, pixmap); + } + } else { + auto pix = wayland_->grab(); + if (!pix.isNull()) { + p.drawPixmap(0, 0, pix); + } else { + combined.fill(Qt::black); + } } SOFT_ASSERT(selector_, return ); diff --git a/src/capture/capturer.h b/src/capture/capturer.h index e49afbe..6f9caed 100644 --- a/src/capture/capturer.h +++ b/src/capture/capturer.h @@ -4,6 +4,8 @@ #include +class WaylandCapturer; + class Capturer { public: @@ -27,4 +29,5 @@ private: const Settings &settings_; QPixmap pixmap_; std::unique_ptr selector_; + std::unique_ptr wayland_; }; diff --git a/src/capture/waylandcapturer.cpp b/src/capture/waylandcapturer.cpp new file mode 100644 index 0000000..78befb3 --- /dev/null +++ b/src/capture/waylandcapturer.cpp @@ -0,0 +1,184 @@ +#include "waylandcapturer.h" +#include "debug.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ +QString desktopFile() +{ + auto name = QCoreApplication::applicationName().toLower() + + QLatin1String("-screenshot-permission"); + name.remove(QLatin1Char(' ')); + const auto result = QDir::homePath() + + QLatin1String("/.local/share/applications/") + name + + QLatin1String(".desktop"); + return result; +} + +void removeDesktop() +{ + QFile::remove(desktopFile()); +} + +void writeDesktop() +{ + QFile f(desktopFile()); + if (!f.open(QFile::WriteOnly)) + return; + + const auto contents = QString(R"([Desktop Entry] +Name=%1-screen-permission +Exec=%2 +X-KDE-DBUS-Restricted-Interfaces=org.kde.kwin.Screenshot +)") + .arg(QCoreApplication::applicationName(), + QCoreApplication::applicationFilePath()); + f.write(contents.toUtf8()); +} + +} // namespace + +bool WaylandCapturer::isWayland() +{ + return qEnvironmentVariable("XDG_SESSION_TYPE").toLower() == + QStringLiteral("wayland"); +} + +WaylandCapturer::Method WaylandCapturer::getMethod() +{ + auto de = qEnvironmentVariable("XDG_CURRENT_DESKTOP").toLower(); + if (de == QLatin1String("kde")) { + return Method::Kde; + } else if (de.endsWith(QLatin1String("gnome"))) { + return Method::Gnome; + } + return Method::Freedesktop; +} + +WaylandCapturer::WaylandCapturer() + : method_(getMethod()) +{ + if (method_ == Method::Kde) + writeDesktop(); +} + +WaylandCapturer::~WaylandCapturer() +{ + if (method_ == Method::Kde) + removeDesktop(); +} + +QPixmap WaylandCapturer::grab() +{ + switch (method_) { + case Method::Gnome: return grabGnome(); + case Method::Kde: return grabKde(); + case Method::Freedesktop: return grabFreedesktop(); + } + return {}; +} + +QPixmap WaylandCapturer::grabKde() +{ + auto request = QDBusMessage::createMethodCall( + QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), + QStringLiteral("org.kde.kwin.Screenshot"), + QStringLiteral("screenshotFullscreen")); + + request << false; + + auto reply = QDBusConnection::sessionBus().call(request); + const auto args = reply.arguments(); + if (reply.type() == QDBusMessage::ErrorMessage || args.isEmpty()) { + LERROR() << "kde capture error" << reply; + return {}; + } + + const auto fileName = args.first().toString(); + auto result = QPixmap(fileName); + QFile::remove(fileName); + return result; +} + +QPixmap WaylandCapturer::grabGnome() +{ + auto request = QDBusMessage::createMethodCall( + QStringLiteral("org.gnome.Shell.Screenshot"), + QStringLiteral("/org/gnome/Shell/Screenshot"), + QStringLiteral("org.gnome.Shell.Screenshot"), + QStringLiteral("Screenshot")); + + QTemporaryFile f; + if (!f.open()) { + LERROR() << "failed to create temp file" << f.errorString(); + return {}; + } + f.close(); + + request << false << false << f.fileName(); + + auto reply = QDBusConnection::sessionBus().call(request); + const auto args = reply.arguments(); + if (reply.type() == QDBusMessage::ErrorMessage || args.isEmpty()) { + LERROR() << "gnome capture error" << reply; + return {}; + } + + if (!args.first().toBool()) { + LERROR() << "gnome capture error"; + return {}; + } + + return QPixmap(f.fileName()); +} + +QPixmap WaylandCapturer::grabFreedesktop() +{ + auto request = QDBusMessage::createMethodCall( + QStringLiteral("org.freedesktop.portal.Desktop"), + QStringLiteral("/org/freedesktop/portal/desktop"), + QStringLiteral("org.freedesktop.portal.Screenshot"), + QStringLiteral("Screenshot")); + + request << QStringLiteral("") << QVariantMap{}; + + auto reply = QDBusConnection::sessionBus().call(request); + const auto args = reply.arguments(); + if (reply.type() == QDBusMessage::ErrorMessage || args.isEmpty()) { + LERROR() << "freedesktop capture error" << reply; + return {}; + } + + const auto handleArg = args.first(); + if (!handleArg.canConvert()) { + LERROR() << "wrong type in freedesktop ansert" << handleArg.userType(); + return {}; + } + + const auto handle = handleArg.value().path(); + + QDBusConnection::sessionBus().connect( + QStringLiteral("org.freedesktop.portal.Desktop"), handle, + QStringLiteral("org.freedesktop.portal.Request"), + QStringLiteral("Response"), this, SLOT(getScreen(uint, QVariantMap))); + + loop_.exec(); + return result_; +} + +void WaylandCapturer::parseFreedesktopResult(uint response, + const QVariantMap &results) +{ + if (response == 0) { + const auto name = QUrl(results["uri"].toString()).toLocalFile(); + result_.load(name); + QFile::remove(name); + } + loop_.exit(); +} diff --git a/src/capture/waylandcapturer.h b/src/capture/waylandcapturer.h new file mode 100644 index 0000000..ccfc187 --- /dev/null +++ b/src/capture/waylandcapturer.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +class WaylandCapturer : public QObject +{ + Q_OBJECT +public: + WaylandCapturer(); + ~WaylandCapturer(); + + static bool isWayland(); + + QPixmap grab(); + +private slots: + void parseFreedesktopResult(uint response, const QVariantMap &results); + +private: + enum class Method { Gnome, Kde, Freedesktop }; + static Method getMethod(); + + QPixmap grabKde(); + QPixmap grabGnome(); + QPixmap grabFreedesktop(); + + Method method_; + QEventLoop loop_; + QPixmap result_; +};