mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
Port to the new org.kde.KWin.ScreenShots2 interface
Fixes https://invent.kde.org/plasma/plasma-phone-components/-/issues/85
This commit is contained in:
parent
393047e905
commit
989d6fdc1d
4 changed files with 282 additions and 77 deletions
|
|
@ -1,4 +1,4 @@
|
|||
qt_add_dbus_interfaces(DBUS_SRCS dbus/org.kde.KWin.Screenshot.xml
|
||||
qt_add_dbus_interfaces(DBUS_SRCS dbus/org.kde.KWin.ScreenShot2.xml
|
||||
dbus/org.kde.KScreen.xml)
|
||||
|
||||
set(phonepanel_SRCS
|
||||
|
|
|
|||
184
containments/panel/dbus/org.kde.KWin.ScreenShot2.xml
Normal file
184
containments/panel/dbus/org.kde.KWin.ScreenShot2.xml
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
-->
|
||||
<node name="/org/kde/KWin/ScreenShot2">
|
||||
<!--
|
||||
org.kde.KWin.ScreenShot2:
|
||||
@short_description: Screen shot interface
|
||||
|
||||
This interface provides a way to request a screenshot of a rectangular area,
|
||||
a screen, or a window.
|
||||
-->
|
||||
<interface name="org.kde.KWin.ScreenShot2">
|
||||
<!--
|
||||
CaptureWindow:
|
||||
@handle: The unique handle that identified the window
|
||||
@options: Optional vardict with screenshot options
|
||||
@pipe: The pipe file descriptor where the screenshot will be written
|
||||
|
||||
Take a screenshot of the specified window. The application that
|
||||
requests the screenshot must have the org.kde.KWin.ScreenShot2
|
||||
interface listed in the X-KDE-DBUS-Restricted-Interfaces desktop
|
||||
file entry.
|
||||
|
||||
Available @options include:
|
||||
|
||||
* "include-cursor" (b): Whether the cursor should be included.
|
||||
Defaults to false
|
||||
* "include-decoration" (b): Whether the decoration should be included.
|
||||
Defaults to false
|
||||
* "native-resolution" (b): Whether the screenshot should be in
|
||||
native size. Defaults to false
|
||||
|
||||
The following results get returned via the @results vardict:
|
||||
|
||||
* "type" (s): The type of the image written to the pipe. Currently,
|
||||
the only supported type is "raw"
|
||||
* "width" (u): The width of the image. Available only if the image
|
||||
type is "raw"
|
||||
* "height" (u): The height of the image. Available only if the image
|
||||
type is "raw"
|
||||
* "stride" (u): The number of bytes per row. Available only if the
|
||||
image type is "raw"
|
||||
* "format" (u): The image format, as defined in QImage::Format.
|
||||
Available only if the image type is "raw"
|
||||
-->
|
||||
<method name="CaptureWindow">
|
||||
<arg name="handle" type="s" direction="in" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap" />
|
||||
<arg name="options" type="a{sv}" direction="in" />
|
||||
<arg name="pipe" type="h" direction="in" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap" />
|
||||
<arg name="results" type="a{sv}" direction="out" />
|
||||
</method>
|
||||
|
||||
<!--
|
||||
CaptureArea:
|
||||
@x: The x coordinate of the upper left corner of the area
|
||||
@y: The y coordinate of the upper left corner of the area
|
||||
@width: The width of the screenshot area
|
||||
@height: The height of the screenshot area
|
||||
@options: Optional vardict with screenshot options
|
||||
@pipe: The pipe file descriptor where the screenshot will be written
|
||||
|
||||
Take a screenshot of the specified rectangular area. The application
|
||||
that requests the screenshot must have the org.kde.KWin.ScreenShot2
|
||||
interface listed in the X-KDE-DBUS-Restricted-Interfaces desktop file
|
||||
entry.
|
||||
|
||||
Available @options include:
|
||||
|
||||
* "include-cursor" (b): Whether the cursor should be included.
|
||||
Defaults to false
|
||||
* "native-resolution" (b): Whether the screenshot should be in
|
||||
native size. Defaults to false
|
||||
|
||||
The following results get returned via the @results vardict:
|
||||
|
||||
* "type" (s): The type of the image written to the pipe. Currently,
|
||||
the only supported type is "raw"
|
||||
* "width" (u): The width of the image. Available only if the image
|
||||
type is "raw"
|
||||
* "height" (u): The height of the image. Available only if the image
|
||||
type is "raw"
|
||||
* "stride" (u): The number of bytes per row. Available only if the
|
||||
image type is "raw"
|
||||
* "format" (u): The image format, as defined in QImage::Format.
|
||||
Available only if the image type is "raw"
|
||||
-->
|
||||
<method name="CaptureArea">
|
||||
<arg name="x" type="i" direction="in" />
|
||||
<arg name="y" type="i" direction="in" />
|
||||
<arg name="width" type="u" direction="in" />
|
||||
<arg name="height" type="u" direction="in" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In4" value="QVariantMap" />
|
||||
<arg name="options" type="a{sv}" direction="in" />
|
||||
<arg name="pipe" type="h" direction="in" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap" />
|
||||
<arg name="results" type="a{sv}" direction="out" />
|
||||
</method>
|
||||
|
||||
<!--
|
||||
CaptureScreen:
|
||||
@name: The name of the screen assigned by the compositor
|
||||
@options: Optional vardict with screenshot options
|
||||
@pipe: The pipe file descriptor where the screenshot will be written
|
||||
|
||||
Take a screenshot of the specified monitor. The application that
|
||||
requests the screenshot must have the org.kde.KWin.ScreenShot2
|
||||
interface listed in the X-KDE-DBUS-Restricted-Interfaces desktop file
|
||||
entry.
|
||||
|
||||
Available @options include:
|
||||
|
||||
* "include-cursor" (b): Whether the cursor should be included.
|
||||
Defaults to false
|
||||
* "native-resolution" (b): Whether the screenshot should be in
|
||||
native size. Defaults to false
|
||||
|
||||
The following results get returned via the @results vardict:
|
||||
|
||||
* "type" (s): The type of the image written to the pipe. Currently,
|
||||
the only supported type is "raw"
|
||||
* "width" (u): The width of the image. Available only if the image
|
||||
type is "raw"
|
||||
* "height" (u): The height of the image. Available only if the image
|
||||
type is "raw"
|
||||
* "stride" (u): The number of bytes per row. Available only if the
|
||||
image type is "raw"
|
||||
* "format" (u): The image format, as defined in QImage::Format.
|
||||
Available only if the image type is "raw"
|
||||
-->
|
||||
<method name="CaptureScreen">
|
||||
<arg name="name" type="s" direction="in" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap" />
|
||||
<arg name="options" type="a{sv}" direction="in" />
|
||||
<arg name="pipe" type="h" direction="in" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap" />
|
||||
<arg name="results" type="a{sv}" direction="out" />
|
||||
</method>
|
||||
|
||||
<!--
|
||||
CaptureInteractive:
|
||||
@kind: 0 - window, 1 - screen
|
||||
@options: Optional vardict with screenshot options
|
||||
@pipe: The pipe file descriptor where the screenshot will be written
|
||||
|
||||
Take a screenshot of a screen or a window as selected by the user.
|
||||
|
||||
Available @options include:
|
||||
|
||||
* "include-cursor" (b): Whether the cursor should be included.
|
||||
Defaults to false
|
||||
* "include-decoration" (b): Whether the decoration should be included.
|
||||
Defaults to false
|
||||
* "native-resolution" (b): Whether the screenshot should be in
|
||||
native size. Defaults to false
|
||||
|
||||
The following results get returned via the @results vardict:
|
||||
|
||||
* "type" (s): The type of the image written to the pipe. Currently,
|
||||
the only supported type is "raw"
|
||||
* "width" (u): The width of the image. Available only if the image
|
||||
type is "raw"
|
||||
* "height" (u): The height of the image. Available only if the image
|
||||
type is "raw"
|
||||
* "stride" (u): The number of bytes per row. Available only if the
|
||||
image type is "raw"
|
||||
* "format" (u): The image format, as defined in QImage::Format.
|
||||
Available only if the image type is "raw"
|
||||
-->
|
||||
<method name="CaptureInteractive">
|
||||
<arg name="kind" type="u" direction="in" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap" />
|
||||
<arg name="options" type="a{sv}" direction="in" />
|
||||
<arg name="pipe" type="h" direction="in" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap" />
|
||||
<arg name="results" type="a{sv}" direction="out" />
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QGuiApplication>
|
||||
#include <QProcess>
|
||||
#include <QScreen>
|
||||
#include <QStandardPaths>
|
||||
|
|
@ -31,44 +32,45 @@ constexpr int SCREENSHOT_DELAY = 200;
|
|||
|
||||
/* -- Static Helpers --------------------------------------------------------------------------- */
|
||||
|
||||
static int readData(int theFile, QByteArray &theDataOut)
|
||||
static QImage allocateImage(const QVariantMap &metadata)
|
||||
{
|
||||
// implementation based on QtWayland file qwaylanddataoffer.cpp
|
||||
char lBuffer[4096];
|
||||
int lRetryCount = 0;
|
||||
ssize_t lBytesRead = 0;
|
||||
bool ok;
|
||||
|
||||
do {
|
||||
// give user 30 sec to click a window, afterwards considered as error
|
||||
while (true) {
|
||||
lBytesRead = QT_READ(theFile, lBuffer, sizeof lBuffer);
|
||||
if (lBytesRead == -1 && (errno == EAGAIN) && ++lRetryCount < 30000) {
|
||||
usleep(1000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lBytesRead > 0) {
|
||||
theDataOut.append(lBuffer, lBytesRead);
|
||||
}
|
||||
} while (lBytesRead > 0);
|
||||
return lBytesRead;
|
||||
}
|
||||
|
||||
static QImage readImage(int thePipeFd)
|
||||
{
|
||||
QByteArray lContent;
|
||||
if (readData(thePipeFd, lContent) != 0) {
|
||||
close(thePipeFd);
|
||||
const uint width = metadata.value(QStringLiteral("width")).toUInt(&ok);
|
||||
if (!ok) {
|
||||
return QImage();
|
||||
}
|
||||
close(thePipeFd);
|
||||
|
||||
QDataStream lDataStream(lContent);
|
||||
QImage lImage;
|
||||
lDataStream >> lImage;
|
||||
return lImage;
|
||||
const uint height = metadata.value(QStringLiteral("height")).toUInt(&ok);
|
||||
if (!ok) {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
const uint format = metadata.value(QStringLiteral("format")).toUInt(&ok);
|
||||
if (!ok) {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
return QImage(width, height, QImage::Format(format));
|
||||
}
|
||||
|
||||
static QImage readImage(int fileDescriptor, const QVariantMap &metadata)
|
||||
{
|
||||
QFile file;
|
||||
if (!file.open(fileDescriptor, QFileDevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
|
||||
close(fileDescriptor);
|
||||
return QImage();
|
||||
}
|
||||
|
||||
QImage result = allocateImage(metadata);
|
||||
if (result.isNull()) {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
QDataStream stream(&file);
|
||||
stream.readRawData(reinterpret_cast<char *>(result.bits()), result.sizeInBytes());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
PhonePanel::PhonePanel(QObject *parent, const QVariantList &args)
|
||||
|
|
@ -76,7 +78,10 @@ PhonePanel::PhonePanel(QObject *parent, const QVariantList &args)
|
|||
{
|
||||
// setHasConfigurationInterface(true);
|
||||
m_kscreenInterface = new org::kde::KScreen(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/kscreen"), QDBusConnection::sessionBus(), this);
|
||||
m_screenshotInterface = new org::kde::kwin::Screenshot(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QDBusConnection::sessionBus(), this);
|
||||
m_screenshotInterface = new OrgKdeKWinScreenShot2Interface(QStringLiteral("org.kde.KWin.ScreenShot2"),
|
||||
QStringLiteral("/org/kde/KWin/ScreenShot2"),
|
||||
QDBusConnection::sessionBus(),
|
||||
this);
|
||||
|
||||
m_localeConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig);
|
||||
m_localeConfigWatcher = KConfigWatcher::create(m_localeConfig);
|
||||
|
|
@ -143,55 +148,70 @@ void PhonePanel::setAutoRotate(bool value)
|
|||
}
|
||||
}
|
||||
|
||||
void PhonePanel::handleMetaDataReceived(const QVariantMap &metadata, int fd)
|
||||
{
|
||||
const QString type = metadata.value(QStringLiteral("type")).toString();
|
||||
if (type != QLatin1String("raw")) {
|
||||
qWarning() << "Unsupported metadata type:" << type;
|
||||
return;
|
||||
}
|
||||
|
||||
auto watcher = new QFutureWatcher<QImage>(this);
|
||||
connect(watcher, &QFutureWatcher<QImage>::finished, this, [watcher]() {
|
||||
watcher->deleteLater();
|
||||
|
||||
QString filePath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
|
||||
if (filePath.isEmpty()) {
|
||||
qWarning() << "Couldn't find a writable location for the screenshot!";
|
||||
return;
|
||||
}
|
||||
QDir picturesDir(filePath);
|
||||
if (!picturesDir.mkpath(QStringLiteral("Screenshots"))) {
|
||||
qWarning() << "Couldn't create folder at" << picturesDir.path() + QStringLiteral("/Screenshots") << "to take screenshot.";
|
||||
return;
|
||||
}
|
||||
filePath += QStringLiteral("/Screenshots/Screenshot_%1.png").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_hhmmss")));
|
||||
const auto m_result = watcher->result();
|
||||
if (m_result.isNull() || !m_result.save(filePath)) {
|
||||
qWarning() << "Screenshot failed";
|
||||
} else {
|
||||
KNotification *notif = new KNotification("captured");
|
||||
notif->setComponentName(QStringLiteral("plasma_phone_components"));
|
||||
notif->setTitle(i18n("New Screenshot"));
|
||||
notif->setUrls({filePath});
|
||||
notif->setText(i18n("New screenshot saved to %1", filePath));
|
||||
notif->sendEvent();
|
||||
}
|
||||
});
|
||||
watcher->setFuture(QtConcurrent::run(readImage, fd, metadata));
|
||||
}
|
||||
|
||||
void PhonePanel::takeScreenshot()
|
||||
{
|
||||
QString filePath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
|
||||
if (filePath.isEmpty()) {
|
||||
qWarning() << "Couldn't find a writable location for the screenshot!";
|
||||
return;
|
||||
}
|
||||
QDir picturesDir(filePath);
|
||||
if (!picturesDir.mkpath(QStringLiteral("Screenshots"))) {
|
||||
qWarning() << "Couldn't create folder at" << picturesDir.path() + QStringLiteral("/Screenshots") << "to take screenshot.";
|
||||
return;
|
||||
}
|
||||
filePath += QStringLiteral("/Screenshots/Screenshot_%1.png").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_hhmmss")));
|
||||
|
||||
// wait ~200 ms to wait for rest of animations
|
||||
QTimer::singleShot(SCREENSHOT_DELAY, [=]() {
|
||||
int lPipeFds[2];
|
||||
if (pipe2(lPipeFds, O_CLOEXEC | O_NONBLOCK) != 0) {
|
||||
if (pipe2(lPipeFds, O_CLOEXEC) != 0) {
|
||||
qWarning() << "Could not take screenshot";
|
||||
return;
|
||||
}
|
||||
// Take fullscreen screenshot, and no pointer
|
||||
QDBusPendingCall pcall = m_screenshotInterface->screenshotFullscreen(QDBusUnixFileDescriptor(lPipeFds[1]), false, true);
|
||||
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [](QDBusPendingCallWatcher *watcher) {
|
||||
if (watcher->isError()) {
|
||||
const auto error = watcher->error();
|
||||
qWarning() << "Error calling KWin DBus interface:" << error.name() << error.message();
|
||||
}
|
||||
watcher->deleteLater();
|
||||
});
|
||||
const auto lWatcher = new QFutureWatcher<QImage>(this);
|
||||
QObject::connect(lWatcher, &QFutureWatcher<QImage>::finished, this, [lWatcher, filePath]() {
|
||||
lWatcher->deleteLater();
|
||||
const QImage lImage = lWatcher->result();
|
||||
qDebug() << lImage;
|
||||
if (!lImage.save(filePath, "PNG")) {
|
||||
qWarning() << "Failed to save screenshot to" << filePath;
|
||||
} else {
|
||||
KNotification *notif = new KNotification("captured");
|
||||
notif->setComponentName(QStringLiteral("plasma_phone_components"));
|
||||
notif->setTitle(i18n("New Screenshot"));
|
||||
notif->setUrls({filePath});
|
||||
notif->setText(i18n("New screenshot saved to %1", filePath));
|
||||
notif->sendEvent();
|
||||
}
|
||||
});
|
||||
lWatcher->setFuture(QtConcurrent::run(readImage, lPipeFds[0]));
|
||||
|
||||
// We don't have access to the ScreenPool so we'll just take the first screen
|
||||
auto pendingCall = m_screenshotInterface->CaptureScreen(qGuiApp->screens().constFirst()->name(), {}, QDBusUnixFileDescriptor(lPipeFds[1]));
|
||||
close(lPipeFds[1]);
|
||||
auto pipeFileDescriptor = lPipeFds[0];
|
||||
|
||||
auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, pipeFileDescriptor]() {
|
||||
watcher->deleteLater();
|
||||
const QDBusPendingReply<QVariantMap> reply = *watcher;
|
||||
|
||||
if (reply.isError()) {
|
||||
qWarning() << "Screenshot request failed:" << reply.error().message();
|
||||
} else {
|
||||
handleMetaDataReceived(reply, pipeFileDescriptor);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
#include <KSharedConfig>
|
||||
|
||||
#include "kscreeninterface.h"
|
||||
#include "screenshotinterface.h"
|
||||
#include "screenshot2interface.h"
|
||||
|
||||
class PhonePanel : public Plasma::Containment
|
||||
{
|
||||
|
|
@ -45,13 +45,14 @@ signals:
|
|||
void isSystem24HourFormatChanged();
|
||||
|
||||
private:
|
||||
void handleMetaDataReceived(const QVariantMap &metadata, int fd);
|
||||
bool m_running = false;
|
||||
|
||||
KConfigWatcher::Ptr m_localeConfigWatcher;
|
||||
KSharedConfig::Ptr m_localeConfig;
|
||||
|
||||
org::kde::KScreen *m_kscreenInterface;
|
||||
org::kde::kwin::Screenshot *m_screenshotInterface;
|
||||
OrgKdeKWinScreenShot2Interface *m_screenshotInterface;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in a new issue