/* * SPDX-FileCopyrightText: 2015 Marco Martin * SPDX-FileCopyrightText: 2018 Bhushan Shah * SPDX-FileCopyrightText: 2021 Devin Lin * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "shellutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FORMAT24H "HH:mm:ss" constexpr int SCREENSHOT_DELAY = 200; /* -- Static Helpers --------------------------------------------------------------------------- */ static QImage allocateImage(const QVariantMap &metadata) { bool ok; 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)); } 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(result.bits()), result.sizeInBytes()); return result; } ShellUtil::ShellUtil(QObject *parent) : QObject{parent} { // setHasConfigurationInterface(true); m_kscreenInterface = new org::kde::KScreen(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/kscreen"), 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); // 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(); } }); } ShellUtil::~ShellUtil() = default; ShellUtil *ShellUtil::instance() { static ShellUtil *inst = new ShellUtil(nullptr); return inst; } void ShellUtil::executeCommand(const QString &command) { qWarning() << "Executing" << command; const QStringList commandAndArguments = QProcess::splitCommand(command); QProcess::startDetached(commandAndArguments.front(), commandAndArguments.mid(1)); } void ShellUtil::toggleTorch() { // FIXME this is hardcoded to the PinePhone for now 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; } write(fd, m_running ? "0" : "1", 1); close(fd); m_running = !m_running; Q_EMIT torchChanged(m_running); } bool ShellUtil::torchEnabled() const { return m_running; } bool ShellUtil::autoRotate() { QDBusPendingReply 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 ShellUtil::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); } } void ShellUtil::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(this); connect(watcher, &QFutureWatcher::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({QUrl::fromLocalFile(filePath)}); notif->setText(i18n("New screenshot saved to %1", filePath)); notif->sendEvent(); } }); watcher->setFuture(QtConcurrent::run(readImage, fd, metadata)); } void ShellUtil::takeScreenshot() { // wait ~200 ms to wait for rest of animations QTimer::singleShot(SCREENSHOT_DELAY, [=]() { int lPipeFds[2]; if (pipe2(lPipeFds, O_CLOEXEC) != 0) { qWarning() << "Could not take screenshot"; return; } // We don't have access to the ScreenPool so we'll just take the first screen QVariantMap options; options.insert(QStringLiteral("native-resolution"), true); auto pendingCall = m_screenshotInterface->CaptureScreen(qGuiApp->screens().constFirst()->name(), options, 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 reply = *watcher; if (reply.isError()) { qWarning() << "Screenshot request failed:" << reply.error().message(); } else { handleMetaDataReceived(reply, pipeFileDescriptor); } }); }); } bool ShellUtil::isSystem24HourFormat() { KConfigGroup localeSettings = KConfigGroup(m_localeConfig, "Locale"); QString timeFormat = localeSettings.readEntry("TimeFormat", QStringLiteral(FORMAT24H)); return timeFormat == QStringLiteral(FORMAT24H); } void ShellUtil::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(); }