diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ I18n Notifications Wayland + WidgetsAddons ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0) diff --git a/data/kde.portal b/data/kde.portal --- a/data/kde.portal +++ b/data/kde.portal @@ -1,4 +1,4 @@ [portal] DBusName=org.freedesktop.impl.portal.desktop.kde -Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Email;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Inhibit;org.freedesktop.impl.portal.Notification;org.freedesktop.impl.portal.Print;org.freedesktop.impl.portal.ScreenCast +Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Email;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Inhibit;org.freedesktop.impl.portal.Notification;org.freedesktop.impl.portal.Print;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.Screenshot UseIn=KDE diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,6 +17,8 @@ print.cpp request.cpp session.cpp + screenshot.cpp + screenshotdialog.cpp ) if (SCREENCAST_ENABLED) @@ -32,6 +34,7 @@ ki18n_wrap_ui(xdg_desktop_portal_kde_SRCS accessdialog.ui + screenshotdialog.ui ) add_executable(xdg-desktop-portal-kde ${xdg_desktop_portal_kde_SRCS}) @@ -45,6 +48,7 @@ KF5::I18n KF5::Notifications KF5::WaylandClient + KF5::WidgetsAddons ) if (SCREENCAST_ENABLED) diff --git a/src/desktopportal.h b/src/desktopportal.h --- a/src/desktopportal.h +++ b/src/desktopportal.h @@ -34,6 +34,7 @@ #if SCREENCAST_ENABLED #include "screencast.h" #endif +#include "screenshot.h" class DesktopPortal : public QObject { @@ -53,6 +54,7 @@ #if SCREENCAST_ENABLED ScreenCastPortal *m_screenCast; #endif + ScreenshotPortal *m_screenshot; }; #endif // XDG_DESKTOP_PORTAL_KDE_DESKTOP_PORTAL_H diff --git a/src/desktopportal.cpp b/src/desktopportal.cpp --- a/src/desktopportal.cpp +++ b/src/desktopportal.cpp @@ -40,6 +40,7 @@ #if SCREENCAST_ENABLED , m_screenCast(new ScreenCastPortal(this)) #endif + , m_screenshot(new ScreenshotPortal(this)) { } diff --git a/src/screenshot.h b/src/screenshot.h new file mode 100644 --- /dev/null +++ b/src/screenshot.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Jan Grulich + */ + +#ifndef XDG_DESKTOP_PORTAL_KDE_SCREENSHOT_H +#define XDG_DESKTOP_PORTAL_KDE_SCREENSHOT_H + +#include +#include + +class ScreenshotPortal : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.impl.portal.Screenshot") +public: + ScreenshotPortal(QObject *parent); + ~ScreenshotPortal(); + +public Q_SLOTS: + uint Screenshot(const QDBusObjectPath &handle, + const QString &app_id, + const QString &parent_window, + const QVariantMap &options, + QVariantMap &results); +}; + +#endif // XDG_DESKTOP_PORTAL_KDE_SCREENSHOT_H diff --git a/src/screenshot.cpp b/src/screenshot.cpp new file mode 100644 --- /dev/null +++ b/src/screenshot.cpp @@ -0,0 +1,86 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Jan Grulich + */ + +#include "screenshot.h" +#include "screenshotdialog.h" + +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(XdgDesktopPortalKdeScreenshot, "xdp-kde-screenshot") + +ScreenshotPortal::ScreenshotPortal(QObject *parent) + : QDBusAbstractAdaptor(parent) +{ +} + +ScreenshotPortal::~ScreenshotPortal() +{ +} + +uint ScreenshotPortal::Screenshot(const QDBusObjectPath &handle, + const QString &app_id, + const QString &parent_window, + const QVariantMap &options, + QVariantMap &results) +{ + qCDebug(XdgDesktopPortalKdeScreenshot) << "Screenshot called with parameters:"; + qCDebug(XdgDesktopPortalKdeScreenshot) << " handle: " << handle.path(); + qCDebug(XdgDesktopPortalKdeScreenshot) << " app_id: " << app_id; + qCDebug(XdgDesktopPortalKdeScreenshot) << " parent_window: " << parent_window; + qCDebug(XdgDesktopPortalKdeScreenshot) << " options: " << options; + + QPointer screenshotDialog = new ScreenshotDialog; + + const bool modal = options.value(QLatin1String("modal"), false).toBool(); + screenshotDialog->setModal(modal); + + const bool interactive = options.value(QLatin1String("interactive"), false).toBool(); + if (!interactive) { + screenshotDialog->takeScreenshot(); + } + + QImage screenshot = screenshotDialog->exec() ? screenshotDialog->image() : QImage(); + + if (screenshotDialog) { + screenshotDialog->deleteLater(); + } + + if (screenshot.isNull()) { + return 1; + } + + const QString filename = QStringLiteral("%1/Screenshot_%2.png").arg(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) + .arg(QDateTime::currentDateTime().toString(QLatin1String("yyyyMMdd_hhmmss"))); + + if (!screenshot.save(filename, "PNG")) { + return 1; + } + + const QString resultFileName = QStringLiteral("file://") + filename; + results.insert(QLatin1String("uri"), resultFileName); + + return 0; +} + + diff --git a/src/desktopportal.cpp b/src/screenshotdialog.h copy from src/desktopportal.cpp copy to src/screenshotdialog.h --- a/src/desktopportal.cpp +++ b/src/screenshotdialog.h @@ -1,5 +1,5 @@ /* - * Copyright © 2016 Red Hat, Inc + * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,31 +18,38 @@ * Jan Grulich */ -#include "desktopportal.h" +#ifndef XDG_DESKTOP_PORTAL_KDE_SCREENSHOT_DIALOG_H +#define XDG_DESKTOP_PORTAL_KDE_SCREENSHOT_DIALOG_H #include -#include -#include -#include -#include - -Q_LOGGING_CATEGORY(XdgDesktopPortalKdeDesktopPortal, "xdp-kde-desktop-portal") - -DesktopPortal::DesktopPortal(QObject *parent) - : QObject(parent) - , m_access(new AccessPortal(this)) - , m_appChooser(new AppChooserPortal(this)) - , m_email(new EmailPortal(this)) - , m_fileChooser(new FileChooserPortal(this)) - , m_inhibit(new InhibitPortal(this)) - , m_notification(new NotificationPortal(this)) - , m_print(new PrintPortal(this)) -#if SCREENCAST_ENABLED - , m_screenCast(new ScreenCastPortal(this)) -#endif + +namespace Ui { +class ScreenshotDialog; } -DesktopPortal::~DesktopPortal() +class ScreenshotDialog : public QDialog { -} + Q_OBJECT +public: + ScreenshotDialog(QDialog *parent = nullptr, Qt::WindowFlags flags = 0); + ~ScreenshotDialog(); + + QImage image() const; + +public Q_SLOTS: + void takeScreenshot(); + +Q_SIGNALS: + void failed(); + +private: + Ui::ScreenshotDialog * m_dialog; + QImage m_image; + + int mask(); +}; + +#endif // XDG_DESKTOP_PORTAL_KDE_SCREENSHOT_DIALOG_H + + diff --git a/src/screenshotdialog.cpp b/src/screenshotdialog.cpp new file mode 100644 --- /dev/null +++ b/src/screenshotdialog.cpp @@ -0,0 +1,147 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Jan Grulich + */ + +#include "screenshotdialog.h" +#include "ui_screenshotdialog.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +Q_LOGGING_CATEGORY(XdgDesktopPortalKdeScreenshotDialog, "xdp-kde-screenshot-dialog") + +static int readData(int fd, QByteArray &data) +{ + // implementation based on QtWayland file qwaylanddataoffer.cpp + char buf[4096]; + int retryCount = 0; + int n; + while (true) { + n = QT_READ(fd, buf, sizeof buf); + // give user 30 sec to click a window, afterwards considered as error + if (n == -1 && (errno == EAGAIN) && ++retryCount < 30000) { + usleep(1000); + } else { + break; + } + } + if (n > 0) { + data.append(buf, n); + n = readData(fd, data); + } + return n; +} + +static QImage readImage(int pipeFd) +{ + QByteArray content; + if (readData(pipeFd, content) != 0) { + close(pipeFd); + return QImage(); + } + close(pipeFd); + QDataStream ds(content); + QImage image; + ds >> image; + return image; +}; + +ScreenshotDialog::ScreenshotDialog(QDialog *parent, Qt::WindowFlags flags) + : QDialog(parent, flags) + , m_dialog(new Ui::ScreenshotDialog) +{ + m_dialog->setupUi(this); + + connect(m_dialog->buttonBox, &QDialogButtonBox::accepted, this, &ScreenshotDialog::accept); + connect(m_dialog->buttonBox, &QDialogButtonBox::rejected, this, &ScreenshotDialog::reject); + connect(m_dialog->takeScreenshotButton, &QPushButton::clicked, this, [this] () { + QTimer::singleShot(1000 * m_dialog->delaySpinBox->value(), this, &ScreenshotDialog::takeScreenshot); + }); + + connect(m_dialog->areaComboBox, static_cast(&QComboBox::currentIndexChanged), this, [this] (int index) { + m_dialog->includeBordersCheckbox->setEnabled(index == 2); + }); + + m_dialog->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); + + setWindowTitle(i18n("Request screenshot")); +} + +ScreenshotDialog::~ScreenshotDialog() +{ + delete m_dialog; +} +void ScreenshotDialog::takeScreenshot() +{ + int pipeFds[2]; + if (pipe2(pipeFds, O_CLOEXEC|O_NONBLOCK) != 0) { + Q_EMIT failed(); + return; + } + + QDBusInterface interface(QLatin1String("org.kde.KWin"), QLatin1String("/Screenshot"), QLatin1String("org.kde.kwin.Screenshot")); + if (m_dialog->areaComboBox->currentIndex() < 2) { + interface.asyncCall(m_dialog->areaComboBox->currentIndex() ? QLatin1String("screenshotScreen") : QLatin1String("screenshotFullscreen"), QVariant::fromValue(QDBusUnixFileDescriptor(pipeFds[1])), m_dialog->includeCursorCheckbox->isChecked()); + } else { + interface.asyncCall(QLatin1String("interactive"), QVariant::fromValue(QDBusUnixFileDescriptor(pipeFds[1])), mask()); + } + + QFutureWatcher *watcher = new QFutureWatcher(this); + QObject::connect(watcher, &QFutureWatcher::finished, this, + [watcher, this] { + watcher->deleteLater(); + m_image = watcher->result(); + m_dialog->image->setPixmap(QPixmap::fromImage(m_image).scaled(400, 320, Qt::KeepAspectRatio)); + m_dialog->buttonBox->button(QDialogButtonBox::Save)->setEnabled(true); + } + ); + + watcher->setFuture(QtConcurrent::run(readImage, pipeFds[0])); + + ::close(pipeFds[1]); +} + +QImage ScreenshotDialog::image() const +{ + return m_image; +} + +int ScreenshotDialog::mask() +{ + int mask = 0; + if (m_dialog->includeBordersCheckbox->isChecked()) { + mask = 1; + } + if (m_dialog->includeCursorCheckbox->isChecked()) { + mask |= 1 << 1; + } + return mask; +} diff --git a/src/screenshotdialog.ui b/src/screenshotdialog.ui new file mode 100644 --- /dev/null +++ b/src/screenshotdialog.ui @@ -0,0 +1,213 @@ + + + ScreenshotDialog + + + + 0 + 0 + 500 + 260 + + + + + 0 + 0 + + + + Dialog + + + + + + <b>Capture Mode</b> + + + + + + + + + + 0 + 0 + + + + Area: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + Full Screen (All Monitors) + + + + + Current Screen + + + + + Active Window + + + + + + + + + 0 + 0 + + + + Delay: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + No Delay + + + 0 + + + + + + + + + <b>Content Options</b> + + + + + + + Include mouse pointer + + + + + + + false + + + Include window borders + + + true + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 75 + + + + + + + + + + + 0 + 0 + + + + Take screenshot + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + Qt::AlignCenter + + + + + + + + KSeparator + QFrame +
kseparator.h
+
+
+ + +