2021-03-01 20:03:25 +00:00
|
|
|
/*
|
|
|
|
|
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
|
|
|
|
|
* SPDX-FileCopyrightText: 2018 Bhushan Shah <bshah@kde.org>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
|
*/
|
2017-09-14 19:03:56 +00:00
|
|
|
|
|
|
|
|
#include "phonepanel.h"
|
|
|
|
|
|
2020-11-11 07:51:25 +00:00
|
|
|
#include <fcntl.h>
|
2020-10-01 07:38:05 +00:00
|
|
|
#include <qplatformdefs.h>
|
2020-11-11 07:51:25 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
2021-03-09 19:04:29 +00:00
|
|
|
#include <KConfigGroup>
|
2021-07-14 16:21:31 +00:00
|
|
|
#include <KIO/ApplicationLauncherJob>
|
2020-12-01 14:25:28 +00:00
|
|
|
#include <KLocalizedString>
|
|
|
|
|
#include <KNotification>
|
|
|
|
|
|
2020-02-06 17:17:47 +00:00
|
|
|
#include <QDBusPendingReply>
|
2020-02-08 12:17:53 +00:00
|
|
|
#include <QDateTime>
|
2020-02-08 16:37:01 +00:00
|
|
|
#include <QDebug>
|
2020-02-06 17:17:47 +00:00
|
|
|
#include <QFile>
|
2021-07-23 01:30:16 +00:00
|
|
|
#include <QGuiApplication>
|
2017-09-14 19:03:56 +00:00
|
|
|
#include <QProcess>
|
2020-02-10 19:45:51 +00:00
|
|
|
#include <QScreen>
|
2020-02-06 17:17:47 +00:00
|
|
|
#include <QStandardPaths>
|
|
|
|
|
#include <QtConcurrent/QtConcurrent>
|
|
|
|
|
|
2021-03-09 19:04:29 +00:00
|
|
|
#define FORMAT24H "HH:mm:ss"
|
|
|
|
|
|
2020-03-20 00:08:59 +00:00
|
|
|
constexpr int SCREENSHOT_DELAY = 200;
|
|
|
|
|
|
2020-10-01 07:38:05 +00:00
|
|
|
/* -- Static Helpers --------------------------------------------------------------------------- */
|
|
|
|
|
|
2021-07-23 01:30:16 +00:00
|
|
|
static QImage allocateImage(const QVariantMap &metadata)
|
2020-10-01 07:38:05 +00:00
|
|
|
{
|
2021-07-23 01:30:16 +00:00
|
|
|
bool ok;
|
2020-10-01 07:38:05 +00:00
|
|
|
|
2021-07-23 01:30:16 +00:00
|
|
|
const uint width = metadata.value(QStringLiteral("width")).toUInt(&ok);
|
|
|
|
|
if (!ok) {
|
|
|
|
|
return QImage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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));
|
2020-10-01 07:38:05 +00:00
|
|
|
}
|
|
|
|
|
|
2021-07-23 01:30:16 +00:00
|
|
|
static QImage readImage(int fileDescriptor, const QVariantMap &metadata)
|
2020-10-01 07:38:05 +00:00
|
|
|
{
|
2021-07-23 01:30:16 +00:00
|
|
|
QFile file;
|
|
|
|
|
if (!file.open(fileDescriptor, QFileDevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
|
|
|
|
|
close(fileDescriptor);
|
2020-10-01 07:38:05 +00:00
|
|
|
return QImage();
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-23 01:30:16 +00:00
|
|
|
QImage result = allocateImage(metadata);
|
|
|
|
|
if (result.isNull()) {
|
|
|
|
|
return QImage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QDataStream stream(&file);
|
|
|
|
|
stream.readRawData(reinterpret_cast<char *>(result.bits()), result.sizeInBytes());
|
|
|
|
|
|
|
|
|
|
return result;
|
2020-10-01 07:38:05 +00:00
|
|
|
}
|
|
|
|
|
|
2017-09-14 19:03:56 +00:00
|
|
|
PhonePanel::PhonePanel(QObject *parent, const QVariantList &args)
|
|
|
|
|
: Plasma::Containment(parent, args)
|
|
|
|
|
{
|
|
|
|
|
// setHasConfigurationInterface(true);
|
2020-04-12 14:14:51 +00:00
|
|
|
m_kscreenInterface = new org::kde::KScreen(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/kscreen"), QDBusConnection::sessionBus(), this);
|
2021-07-23 01:30:16 +00:00
|
|
|
m_screenshotInterface = new OrgKdeKWinScreenShot2Interface(QStringLiteral("org.kde.KWin.ScreenShot2"),
|
|
|
|
|
QStringLiteral("/org/kde/KWin/ScreenShot2"),
|
|
|
|
|
QDBusConnection::sessionBus(),
|
|
|
|
|
this);
|
2021-03-19 07:52:22 +00:00
|
|
|
|
2021-03-09 19:04:29 +00:00
|
|
|
m_localeConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig);
|
|
|
|
|
m_localeConfigWatcher = KConfigWatcher::create(m_localeConfig);
|
2021-03-19 07:52:22 +00:00
|
|
|
|
2021-03-09 19:04:29 +00:00
|
|
|
// watch for changes to locale config, to update 12/24 hour time
|
|
|
|
|
connect(m_localeConfigWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void {
|
|
|
|
|
if (group.name() == "Locale") {
|
|
|
|
|
// we have to reparse for new changes (from system settings)
|
|
|
|
|
m_localeConfig->reparseConfiguration();
|
|
|
|
|
Q_EMIT isSystem24HourFormatChanged();
|
|
|
|
|
}
|
|
|
|
|
});
|
2017-09-14 19:03:56 +00:00
|
|
|
}
|
|
|
|
|
|
2020-03-20 00:08:59 +00:00
|
|
|
PhonePanel::~PhonePanel() = default;
|
2017-09-14 19:03:56 +00:00
|
|
|
|
|
|
|
|
void PhonePanel::executeCommand(const QString &command)
|
|
|
|
|
{
|
2020-03-20 00:08:59 +00:00
|
|
|
qWarning() << "Executing" << command;
|
2020-10-19 16:03:59 +00:00
|
|
|
const QStringList commandAndArguments = QProcess::splitCommand(command);
|
|
|
|
|
QProcess::startDetached(commandAndArguments.front(), commandAndArguments.mid(1));
|
2017-09-14 19:03:56 +00:00
|
|
|
}
|
|
|
|
|
|
2018-11-12 04:02:46 +00:00
|
|
|
void PhonePanel::toggleTorch()
|
|
|
|
|
{
|
2020-11-11 12:52:19 +00:00
|
|
|
// FIXME this is hardcoded to the PinePhone for now
|
2020-11-11 07:51:25 +00:00
|
|
|
static auto FLASH_SYSFS_PATH = "/sys/devices/platform/led-controller/leds/white:flash/brightness";
|
|
|
|
|
int fd = open(FLASH_SYSFS_PATH, O_WRONLY);
|
|
|
|
|
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
qWarning() << "Unable to open file %s" << FLASH_SYSFS_PATH;
|
|
|
|
|
return;
|
2018-11-12 04:02:46 +00:00
|
|
|
}
|
2020-11-11 07:51:25 +00:00
|
|
|
|
|
|
|
|
write(fd, m_running ? "0" : "1", 1);
|
|
|
|
|
close(fd);
|
|
|
|
|
m_running = !m_running;
|
2020-11-16 06:20:40 +00:00
|
|
|
Q_EMIT torchChanged(m_running);
|
|
|
|
|
}
|
|
|
|
|
bool PhonePanel::torchEnabled() const
|
|
|
|
|
{
|
|
|
|
|
return m_running;
|
2018-11-12 04:02:46 +00:00
|
|
|
}
|
2020-04-12 14:14:51 +00:00
|
|
|
bool PhonePanel::autoRotate()
|
|
|
|
|
{
|
|
|
|
|
QDBusPendingReply<bool> reply = m_kscreenInterface->getAutoRotate();
|
|
|
|
|
reply.waitForFinished();
|
|
|
|
|
if (reply.isError()) {
|
|
|
|
|
qWarning() << "Getting auto rotate failed:" << reply.error().name() << reply.error().message();
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
return reply.value();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PhonePanel::setAutoRotate(bool value)
|
|
|
|
|
{
|
|
|
|
|
QDBusPendingReply<> reply = m_kscreenInterface->setAutoRotate(value);
|
|
|
|
|
reply.waitForFinished();
|
|
|
|
|
if (reply.isError()) {
|
|
|
|
|
qWarning() << "Setting auto rotate failed:" << reply.error().name() << reply.error().message();
|
|
|
|
|
} else {
|
|
|
|
|
emit autoRotateChanged(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-23 01:30:16 +00:00
|
|
|
void PhonePanel::handleMetaDataReceived(const QVariantMap &metadata, int fd)
|
2020-02-06 17:17:47 +00:00
|
|
|
{
|
2021-07-23 01:30:16 +00:00
|
|
|
const QString type = metadata.value(QStringLiteral("type")).toString();
|
|
|
|
|
if (type != QLatin1String("raw")) {
|
|
|
|
|
qWarning() << "Unsupported metadata type:" << type;
|
2020-10-01 07:38:05 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-23 01:30:16 +00:00
|
|
|
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"));
|
2021-10-02 12:01:56 +00:00
|
|
|
notif->setUrls({QUrl::fromLocalFile(filePath)});
|
2021-07-23 01:30:16 +00:00
|
|
|
notif->setText(i18n("New screenshot saved to %1", filePath));
|
|
|
|
|
notif->sendEvent();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
watcher->setFuture(QtConcurrent::run(readImage, fd, metadata));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PhonePanel::takeScreenshot()
|
|
|
|
|
{
|
2020-02-08 12:45:33 +00:00
|
|
|
// wait ~200 ms to wait for rest of animations
|
2020-03-20 00:08:59 +00:00
|
|
|
QTimer::singleShot(SCREENSHOT_DELAY, [=]() {
|
2020-10-01 07:38:05 +00:00
|
|
|
int lPipeFds[2];
|
2021-07-23 01:30:16 +00:00
|
|
|
if (pipe2(lPipeFds, O_CLOEXEC) != 0) {
|
2020-10-01 07:38:05 +00:00
|
|
|
qWarning() << "Could not take screenshot";
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-07-23 01:30:16 +00:00
|
|
|
|
|
|
|
|
// 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]() {
|
2020-02-08 12:45:33 +00:00
|
|
|
watcher->deleteLater();
|
2021-07-23 01:30:16 +00:00
|
|
|
const QDBusPendingReply<QVariantMap> reply = *watcher;
|
|
|
|
|
|
|
|
|
|
if (reply.isError()) {
|
|
|
|
|
qWarning() << "Screenshot request failed:" << reply.error().message();
|
2020-12-01 14:25:28 +00:00
|
|
|
} else {
|
2021-07-23 01:30:16 +00:00
|
|
|
handleMetaDataReceived(reply, pipeFileDescriptor);
|
2020-10-01 07:38:05 +00:00
|
|
|
}
|
|
|
|
|
});
|
2020-02-06 17:17:47 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-09 19:04:29 +00:00
|
|
|
bool PhonePanel::isSystem24HourFormat()
|
|
|
|
|
{
|
|
|
|
|
KConfigGroup localeSettings = KConfigGroup(m_localeConfig, "Locale");
|
2021-03-19 07:52:22 +00:00
|
|
|
|
2021-03-09 19:04:29 +00:00
|
|
|
QString timeFormat = localeSettings.readEntry("TimeFormat", QStringLiteral(FORMAT24H));
|
|
|
|
|
return timeFormat == QStringLiteral(FORMAT24H);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-14 16:21:31 +00:00
|
|
|
void PhonePanel::launchApp(const QString &app)
|
|
|
|
|
{
|
|
|
|
|
const KService::Ptr appService = KService::serviceByDesktopName(app);
|
|
|
|
|
if (!appService) {
|
|
|
|
|
qWarning() << "Could not find" << app;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto job = new KIO::ApplicationLauncherJob(appService, this);
|
|
|
|
|
job->start();
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-19 04:52:49 +00:00
|
|
|
K_PLUGIN_CLASS_WITH_JSON(PhonePanel, "metadata.json")
|
2017-09-14 19:03:56 +00:00
|
|
|
|
|
|
|
|
#include "phonepanel.moc"
|