// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors #include "screengrabber.h" #include "abstractlogger.h" #include "src/core/qguiappcurrentscreen.h" #include "src/utils/filenamehandler.h" #include "src/utils/systemnotification.h" #include #include #include #include #include #include #if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) #include "request.h" #include #include #include #include #include #endif ScreenGrabber::ScreenGrabber(QObject* parent) : QObject(parent) {} void ScreenGrabber::generalGrimScreenshot(bool& ok, QPixmap& res) { #ifdef USE_WAYLAND_GRIM #if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) QProcess Process; QString program = "grim"; QStringList arguments; arguments << "-"; Process.start(program, arguments); if (Process.waitForFinished()) { res.loadFromData(Process.readAll()); ok = true; } else { ok = false; AbstractLogger::error() << tr("The universal wayland screen capture adapter requires Grim as " "the screen capture component of wayland. If the screen " "capture component is missing, please install it!"); } #endif #endif } void ScreenGrabber::freeDesktopPortal(bool& ok, QPixmap& res) { #if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) QDBusInterface screenshotInterface( QStringLiteral("org.freedesktop.portal.Desktop"), QStringLiteral("/org/freedesktop/portal/desktop"), QStringLiteral("org.freedesktop.portal.Screenshot")); // unique token QString token = QUuid::createUuid().toString().remove('-').remove('{').remove('}'); // premake interface auto* request = new OrgFreedesktopPortalRequestInterface( QStringLiteral("org.freedesktop.portal.Desktop"), "/org/freedesktop/portal/desktop/request/" + QDBusConnection::sessionBus().baseService().remove(':').replace('.', '_') + "/" + token, QDBusConnection::sessionBus(), this); QEventLoop loop; const auto gotSignal = [&res, &loop](uint status, const QVariantMap& map) { if (status == 0) { // Parse this as URI to handle unicode properly QUrl uri = map.value("uri").toString(); QString uriString = uri.toLocalFile(); res = QPixmap(uriString); res.setDevicePixelRatio(qApp->devicePixelRatio()); QFile imgFile(uriString); imgFile.remove(); } loop.quit(); }; // prevent racy situations and listen before calling screenshot QMetaObject::Connection conn = QObject::connect( request, &org::freedesktop::portal::Request::Response, gotSignal); screenshotInterface.call( QStringLiteral("Screenshot"), "", QMap({ { "handle_token", QVariant(token) }, { "interactive", QVariant(false) } })); loop.exec(); QObject::disconnect(conn); request->Close().waitForFinished(); request->deleteLater(); if (res.isNull()) { ok = false; } #endif } QPixmap ScreenGrabber::grabEntireDesktop(bool& ok) { ok = true; #if defined(Q_OS_MACOS) QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); QPixmap screenPixmap( currentScreen->grabWindow(QApplication::desktop()->winId(), currentScreen->geometry().x(), currentScreen->geometry().y(), currentScreen->geometry().width(), currentScreen->geometry().height())); screenPixmap.setDevicePixelRatio(currentScreen->devicePixelRatio()); return screenPixmap; #elif defined(Q_OS_LINUX) || defined(Q_OS_UNIX) if (m_info.waylandDetected()) { QPixmap res; // handle screenshot based on DE switch (m_info.windowManager()) { case DesktopInfo::GNOME: case DesktopInfo::KDE: freeDesktopPortal(ok, res); break; case DesktopInfo::QTILE: case DesktopInfo::SWAY: case DesktopInfo::HYPRLAND: case DesktopInfo::OTHER: { #ifndef USE_WAYLAND_GRIM AbstractLogger::warning() << tr( "If the USE_WAYLAND_GRIM option is not activated, the dbus " "protocol will be used. It should be noted that using the " "dbus protocol under wayland is not recommended. It is " "recommended to recompile with the USE_WAYLAND_GRIM flag to " "activate the grim-based general wayland screenshot adapter"); freeDesktopPortal(ok, res); #else AbstractLogger::warning() << tr("grim's screenshot component is implemented based on " "wlroots, it may not be used in GNOME or similar " "desktop environments"); generalGrimScreenshot(ok, res); #endif break; } default: ok = false; AbstractLogger::error() << tr("Unable to detect desktop environment (GNOME? KDE? " "Qile? Sway? ...)"); AbstractLogger::error() << tr("Hint: try setting the XDG_CURRENT_DESKTOP environment " "variable."); break; } if (!ok) { AbstractLogger::error() << tr("Unable to capture screen"); } return res; } #endif #if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) || defined(Q_OS_WIN) QRect geometry = desktopGeometry(); QPixmap p(QApplication::primaryScreen()->grabWindow( QApplication::desktop()->winId(), geometry.x(), geometry.y(), geometry.width(), geometry.height())); auto screenNumber = QApplication::desktop()->screenNumber(); QScreen* screen = QApplication::screens()[screenNumber]; p.setDevicePixelRatio(screen->devicePixelRatio()); return p; #endif } QRect ScreenGrabber::screenGeometry(QScreen* screen) { QPixmap p; QRect geometry; if (m_info.waylandDetected()) { QPoint topLeft(0, 0); #ifdef Q_OS_WIN for (QScreen* const screen : QGuiApplication::screens()) { QPoint topLeftScreen = screen->geometry().topLeft(); if (topLeft.x() > topLeftScreen.x() || topLeft.y() > topLeftScreen.y()) { topLeft = topLeftScreen; } } #endif geometry = screen->geometry(); geometry.moveTo(geometry.topLeft() - topLeft); } else { QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); geometry = currentScreen->geometry(); } return geometry; } QPixmap ScreenGrabber::grabScreen(QScreen* screen, bool& ok) { QPixmap p; QRect geometry = screenGeometry(screen); if (m_info.waylandDetected()) { p = grabEntireDesktop(ok); if (ok) { return p.copy(geometry); } } else { ok = true; return screen->grabWindow(QApplication::desktop()->winId(), geometry.x(), geometry.y(), geometry.width(), geometry.height()); } return p; } QRect ScreenGrabber::desktopGeometry() { QRect geometry; for (QScreen* const screen : QGuiApplication::screens()) { QRect scrRect = screen->geometry(); scrRect.moveTo(scrRect.x() / screen->devicePixelRatio(), scrRect.y() / screen->devicePixelRatio()); geometry = geometry.united(scrRect); } return geometry; }