Commit 06ceacee authored by Jan Grulich's avatar Jan Grulich
Browse files

Add Screenshot portal

Summary: Add Screenshot portal using KWin Screenshot DBus interface.

Reviewers: #plasma, graesslin, davidedmundson, romangg, Kanedias

Reviewed By: Kanedias

Subscribers: anthonyfieroni, #plasma

Differential Revision: https://phabricator.kde.org/D12068
parent 7bd8db5c
......@@ -71,6 +71,7 @@ find_package(KF5 REQUIRED
I18n
Notifications
Wayland
WidgetsAddons
)
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0)
......
[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
......@@ -17,6 +17,8 @@ set(xdg_desktop_portal_kde_SRCS
print.cpp
request.cpp
session.cpp
screenshot.cpp
screenshotdialog.cpp
)
if (SCREENCAST_ENABLED)
......@@ -32,6 +34,7 @@ endif()
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 @@ target_link_libraries(xdg-desktop-portal-kde
KF5::I18n
KF5::Notifications
KF5::WaylandClient
KF5::WidgetsAddons
)
if (SCREENCAST_ENABLED)
......
......@@ -40,6 +40,7 @@ DesktopPortal::DesktopPortal(QObject *parent)
#if SCREENCAST_ENABLED
, m_screenCast(new ScreenCastPortal(this))
#endif
, m_screenshot(new ScreenshotPortal(this))
{
}
......
......@@ -34,6 +34,7 @@
#if SCREENCAST_ENABLED
#include "screencast.h"
#endif
#include "screenshot.h"
class DesktopPortal : public QObject
{
......@@ -53,6 +54,7 @@ private:
#if SCREENCAST_ENABLED
ScreenCastPortal *m_screenCast;
#endif
ScreenshotPortal *m_screenshot;
};
#endif // XDG_DESKTOP_PORTAL_KDE_DESKTOP_PORTAL_H
......
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#include "screenshot.h"
#include "screenshotdialog.h"
#include <QDateTime>
#include <QLoggingCategory>
#include <QTemporaryFile>
#include <QStandardPaths>
#include <QPointer>
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> 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;
}
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#ifndef XDG_DESKTOP_PORTAL_KDE_SCREENSHOT_H
#define XDG_DESKTOP_PORTAL_KDE_SCREENSHOT_H
#include <QDBusAbstractAdaptor>
#include <QDBusObjectPath>
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
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#include "screenshotdialog.h"
#include "ui_screenshotdialog.h"
#include <QLoggingCategory>
#include <QPushButton>
#include <QDBusInterface>
#include <QDBusPendingCall>
#include <QDBusPendingReply>
#include <QDBusUnixFileDescriptor>
#include <QTimer>
#include <QFutureWatcher>
#include <QtConcurrent/QtConcurrentRun>
#include <qplatformdefs.h>
#include <fcntl.h>
#include <unistd.h>
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<void (QComboBox::*)(int)>(&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<QImage> *watcher = new QFutureWatcher<QImage>(this);
QObject::connect(watcher, &QFutureWatcher<QImage>::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;
}
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#ifndef XDG_DESKTOP_PORTAL_KDE_SCREENSHOT_DIALOG_H
#define XDG_DESKTOP_PORTAL_KDE_SCREENSHOT_DIALOG_H
#include <QDialog>
namespace Ui
{
class ScreenshotDialog;
}
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
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScreenshotDialog</class>
<widget class="QDialog" name="ScreenshotDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>260</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;b&gt;Capture Mode&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Area:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="areaComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Full Screen (All Monitors)</string>
</property>
</item>
<item>
<property name="text">
<string>Current Screen</string>
</property>
</item>
<item>
<property name="text">
<string>Active Window</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Delay:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="delaySpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="specialValueText">
<string>No Delay</string>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;b&gt;Content Options&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="includeCursorCheckbox">
<property name="text">
<string>Include mouse pointer</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="includeBordersCheckbox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Include window borders</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout"/>
</item>
<item row="7" column="0">
<widget class="KSeparator" name="kseparator"/>
</item>
<item row="7" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>75</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="takeScreenshotButton">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Take screenshot</string>
</property>
<property name="icon">
<iconset theme="video-display"/>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="1">
<widget class="QLabel" name="image">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KSeparator</class>
<extends>QFrame</extends>
<header>kseparator.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment