File indexing completed on 2024-02-25 17:31:00

0001 // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0002 // SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0003 
0004 #include "dynamiclauncher.h"
0005 
0006 #include <optional>
0007 
0008 #include <QFile>
0009 #include <QGuiApplication>
0010 #include <QUrl>
0011 #include <QWindow>
0012 
0013 #include <KIconLoader>
0014 #include <KLocalizedString>
0015 
0016 #include "dynamiclauncher_debug.h"
0017 #include "dynamiclauncherdialog.h"
0018 #include "portalicon.h"
0019 #include "request.h"
0020 #include "utils.h"
0021 
0022 DynamicLauncherPortal::DynamicLauncherPortal(QObject *parent)
0023     : QDBusAbstractAdaptor(parent)
0024 {
0025     PortalIcon::registerDBusType();
0026 }
0027 
0028 // We want explicit support for types lest we incorrectly demarshal something.
0029 template<typename T>
0030 std::optional<T> readOption(const QString &key, const QVariantMap &options) = delete;
0031 
0032 template<>
0033 std::optional<bool> readOption(const QString &key, const QVariantMap &options)
0034 {
0035     if (options.contains(key)) {
0036         return options.value(key).toBool();
0037     }
0038     return std::nullopt;
0039 }
0040 
0041 template<>
0042 std::optional<QString> readOption(const QString &key, const QVariantMap &options)
0043 {
0044     if (options.contains(key)) {
0045         return options.value(key).toString();
0046     }
0047     return std::nullopt;
0048 }
0049 
0050 template<>
0051 std::optional<uint> readOption(const QString &key, const QVariantMap &options)
0052 {
0053     if (options.contains(key)) {
0054         return options.value(key).toUInt();
0055     }
0056     return std::nullopt;
0057 }
0058 
0059 QIcon static extractIcon(const QDBusVariant &iconVariant)
0060 {
0061     auto icon = qdbus_cast<PortalIcon>(iconVariant.variant());
0062     const QVariant iconData = icon.data.variant();
0063     // NB: The DynamicLauncher portal only accept GByteIcons, i.e. the only type we'll ever get are bytes.
0064     if (icon.str == QStringLiteral("bytes") && iconData.type() == QVariant::ByteArray) {
0065         QPixmap pixmap;
0066         pixmap.loadFromData(iconData.toByteArray());
0067         return pixmap;
0068     }
0069     return {};
0070 }
0071 
0072 static QString typeToTitle(uint type)
0073 {
0074     switch (static_cast<DynamicLauncherPortal::Type>(type)) {
0075     case DynamicLauncherPortal::Type::Webapp:
0076         return i18nc("@title", "Add Web Application…");
0077     case DynamicLauncherPortal::Type::Application:
0078         break;
0079     }
0080     // Default value is Application; we treat all unmapped, possibly future, types as generic Application.
0081     return i18nc("@title", "Add Application…");
0082 }
0083 
0084 static QByteArray iconFromName(const QString &name)
0085 {
0086     static constexpr auto maxSize = 512; // the portal never deals with larger icons; they'd likely be SVGs at that point anyway.
0087     const auto iconPath = KIconLoader::global()->iconPath(name, -maxSize, false);
0088     QFile iconFile(iconPath);
0089     if (!iconFile.open(QFile::ReadOnly)) {
0090         qWarning() << "Failed to read icon" << iconPath;
0091         return {};
0092     }
0093     return iconFile.readAll();
0094 }
0095 
0096 uint DynamicLauncherPortal::PrepareInstall(const QDBusObjectPath &handle,
0097                                            const QString &app_id,
0098                                            const QString &parent_window,
0099                                            const QString &name,
0100                                            const QDBusVariant &iconVariant,
0101                                            const QVariantMap &options,
0102                                            QVariantMap &results)
0103 {
0104     qCDebug(XdgDesktopPortalKdeDynamicLauncher) << "PrepareInstall called with parameters:";
0105     qCDebug(XdgDesktopPortalKdeDynamicLauncher) << "    handle: " << handle.path();
0106     qCDebug(XdgDesktopPortalKdeDynamicLauncher) << "    app_id: " << app_id;
0107     qCDebug(XdgDesktopPortalKdeDynamicLauncher) << "    parent_window: " << parent_window;
0108     qCDebug(XdgDesktopPortalKdeDynamicLauncher) << "    name: " << name;
0109     qCDebug(XdgDesktopPortalKdeDynamicLauncher) << "    iconVariant: " << iconVariant.variant();
0110     qCDebug(XdgDesktopPortalKdeDynamicLauncher) << "    options: " << options;
0111 
0112     const auto modal = readOption<bool>(QStringLiteral("modal"), options).value_or(true);
0113     const auto launcherType = readOption<uint>(QStringLiteral("launcher_type"), options).value_or(uint(Type::Application));
0114     const auto optionalTarget = readOption<QString>(QStringLiteral("target"), options);
0115     const auto editableName = readOption<bool>(QStringLiteral("editable_name"), options).value_or(true);
0116     const auto editableIcon = readOption<bool>(QStringLiteral("editable_icon"), options).value_or(false);
0117 
0118     static const QString nameKey = QStringLiteral("name");
0119     static const QString iconKey = QStringLiteral("icon");
0120     results[nameKey] = name;
0121     results[iconKey] = QVariant::fromValue(iconVariant);
0122 
0123     const auto icon = extractIcon(iconVariant);
0124 
0125     DynamicLauncherDialog dialog(typeToTitle(launcherType), icon, name, optionalTarget ? QUrl(optionalTarget.value()) : QUrl());
0126     dialog.windowHandle()->setModality(modal ? Qt::WindowModal : Qt::NonModal);
0127     Utils::setParentWindow(dialog.windowHandle(), parent_window);
0128     Request::makeClosableDialogRequest(handle, &dialog);
0129 
0130     const bool result = dialog.exec();
0131 
0132     if (editableName) {
0133         results[nameKey] = dialog.m_name;
0134     }
0135 
0136     if (editableIcon && dialog.m_icon != icon && dialog.m_icon.type() == QVariant::String) {
0137         const auto data = iconFromName(dialog.m_icon.toString());
0138         if (!data.isEmpty()) {
0139             const PortalIcon portalIcon{"bytes", QDBusVariant(data)};
0140             results[iconKey] = QVariant::fromValue(QDBusVariant(QVariant::fromValue(portalIcon)));
0141         }
0142     }
0143 
0144     return result ? 0 : 1;
0145 }
0146 
0147 uint DynamicLauncherPortal::RequestInstallToken(const QString &app_id, const QVariantMap &options)
0148 {
0149     Q_UNUSED(options);
0150 
0151     // Blanket allow certain apps to create app entries. Ported from GTK portal.
0152     // Perhaps this should also include browsers? Unclear at the time of writing.
0153 
0154     static QStringList allowedIDs = {
0155         QStringLiteral("org.gnome.Software"),
0156         QStringLiteral("org.gnome.SoftwareDevel"),
0157         QStringLiteral("io.elementary.appcenter"),
0158         QStringLiteral("org.kde.discover"),
0159     };
0160 
0161     return allowedIDs.contains(app_id) ? 0 : 2;
0162 }