File indexing completed on 2024-05-19 16:31:40

0001 /*
0002  *  SPDX-FileCopyrightText: 2008-2009 Lukas Appelhans <l.appelhans@gmx.de>
0003  *  SPDX-FileCopyrightText: 2010-2011 Ingomar Wesp <ingomar@wesp.name>
0004  *  SPDX-FileCopyrightText: 2013 Bhushan Shah <bhush94@gmail.com>
0005  *  SPDX-FileCopyrightText: 2015 David Rosca <nowrep@gmail.com>
0006  *
0007  *  SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0008  */
0009 
0010 #include "quicklaunch_p.h"
0011 
0012 #include <QDir>
0013 #include <QFileInfo>
0014 #include <QMimeDatabase>
0015 #include <QMimeType>
0016 #include <QRandomGenerator>
0017 #include <QStandardPaths>
0018 
0019 #include <KConfig>
0020 #include <KConfigGroup>
0021 #include <KDesktopFile>
0022 #include <KFileItem>
0023 #include <KIO/CommandLauncherJob>
0024 #include <KIO/OpenUrlJob>
0025 #include <KNotificationJobUiDelegate>
0026 #include <KOpenWithDialog>
0027 #include <KPropertiesDialog>
0028 
0029 #include <kio/global.h>
0030 
0031 QuicklaunchPrivate::QuicklaunchPrivate(QObject *parent)
0032     : QObject(parent)
0033 {
0034 }
0035 
0036 QVariantMap QuicklaunchPrivate::launcherData(const QUrl &url)
0037 {
0038     QString name;
0039     QString icon;
0040     QString genericName;
0041     QVariantList jumpListActions;
0042 
0043     if (url.scheme() == QLatin1String("quicklaunch")) {
0044         // Ignore internal scheme
0045     } else if (url.isLocalFile()) {
0046         const KFileItem fileItem(url);
0047         const QFileInfo fi(url.toLocalFile());
0048 
0049         if (fileItem.isDesktopFile()) {
0050             const KDesktopFile f(url.toLocalFile());
0051             name = f.readName();
0052             icon = f.readIcon();
0053             genericName = f.readGenericName();
0054             if (name.isEmpty()) {
0055                 name = QFileInfo(url.toLocalFile()).fileName();
0056             }
0057 
0058             const QStringList &actions = f.readActions();
0059 
0060             for (const QString &actionName : actions) {
0061                 const KConfigGroup &actionGroup = f.actionGroup(actionName);
0062 
0063                 if (!actionGroup.isValid() || !actionGroup.exists()) {
0064                     continue;
0065                 }
0066 
0067                 const QString &name = actionGroup.readEntry("Name");
0068                 const QString &exec = actionGroup.readEntry("Exec");
0069                 if (name.isEmpty() || exec.isEmpty()) {
0070                     continue;
0071                 }
0072 
0073                 jumpListActions << QVariantMap{{QStringLiteral("name"), name},
0074                                                {QStringLiteral("icon"), actionGroup.readEntry("Icon")},
0075                                                {QStringLiteral("exec"), exec}};
0076             }
0077         } else {
0078             QMimeDatabase db;
0079             name = fi.baseName();
0080             icon = db.mimeTypeForUrl(url).iconName();
0081             genericName = fi.baseName();
0082         }
0083     } else {
0084         if (url.scheme().contains(QLatin1String("http"))) {
0085             name = url.host();
0086         } else if (name.isEmpty()) {
0087             name = url.toString();
0088             if (name.endsWith(QLatin1String(":/"))) {
0089                 name = url.scheme();
0090             }
0091         }
0092         icon = KIO::iconNameForUrl(url);
0093     }
0094 
0095     return QVariantMap{{QStringLiteral("applicationName"), name},
0096                        {QStringLiteral("iconName"), icon},
0097                        {QStringLiteral("genericName"), genericName},
0098                        {QStringLiteral("jumpListActions"), jumpListActions}};
0099 }
0100 
0101 void QuicklaunchPrivate::openUrl(const QUrl &url)
0102 {
0103     auto *job = new KIO::OpenUrlJob(url);
0104     job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled));
0105     job->setRunExecutables(true);
0106     job->start();
0107 }
0108 
0109 void QuicklaunchPrivate::openExec(const QString &exec)
0110 {
0111     KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(exec);
0112     job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled));
0113     job->start();
0114 }
0115 
0116 void QuicklaunchPrivate::addLauncher(bool isPopup)
0117 {
0118     KOpenWithDialog *dialog = new KOpenWithDialog();
0119     dialog->setModal(false);
0120     dialog->setAttribute(Qt::WA_DeleteOnClose);
0121     dialog->hideRunInTerminal();
0122     dialog->setSaveNewApplications(true);
0123     dialog->show();
0124 
0125     connect(dialog, &KOpenWithDialog::accepted, this, [this, dialog, isPopup]() {
0126         if (!dialog->service()) {
0127             return;
0128         }
0129         const QUrl &url = QUrl::fromLocalFile(dialog->service()->entryPath());
0130         if (url.isValid()) {
0131             Q_EMIT launcherAdded(url.toString(), isPopup);
0132         }
0133     });
0134 }
0135 
0136 static QString locateLocal(const QString &file)
0137 {
0138     const QString &dataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0139     const QString appDataPath = QStringLiteral("%1/quicklaunch").arg(dataPath);
0140     QDir().mkpath(appDataPath);
0141     return QStringLiteral("%1/%2").arg(appDataPath, file);
0142 }
0143 
0144 static QString determineNewDesktopFilePath(const QString &baseName)
0145 {
0146     QString appendix;
0147     QString desktopFilePath = locateLocal(baseName) + QLatin1String(".desktop");
0148 
0149     auto *generator = QRandomGenerator::global();
0150     while (QFile::exists(desktopFilePath)) {
0151         if (appendix.isEmpty()) {
0152             appendix += QLatin1Char('-');
0153         }
0154 
0155         // Limit to [0-9] and [a-z] range.
0156         char newChar = generator->bounded(36);
0157         newChar += newChar < 10 ? 48 : 97 - 10;
0158         appendix += QLatin1Char(newChar);
0159 
0160         desktopFilePath = locateLocal(baseName + appendix + QLatin1String(".desktop"));
0161     }
0162 
0163     return desktopFilePath;
0164 }
0165 
0166 void QuicklaunchPrivate::editLauncher(QUrl url, int index, bool isPopup)
0167 {
0168     // If the launcher does not point to a desktop file, create one,
0169     // so that user can change url, icon, text and description.
0170     bool desktopFileCreated = false;
0171 
0172     if (!url.isLocalFile() || !KDesktopFile::isDesktopFile(url.toLocalFile())) {
0173         const QString desktopFilePath = determineNewDesktopFilePath(QStringLiteral("launcher"));
0174         const QVariantMap data = launcherData(url);
0175 
0176         KConfig desktopFile(desktopFilePath);
0177         KConfigGroup desktopEntry(&desktopFile, "Desktop Entry");
0178 
0179         desktopEntry.writeEntry("Name", data.value(QStringLiteral("applicationName")).toString());
0180         desktopEntry.writeEntry("Comment", data.value(QStringLiteral("genericName")).toString());
0181         desktopEntry.writeEntry("Icon", data.value(QStringLiteral("iconName")).toString());
0182         desktopEntry.writeEntry("Type", "Link");
0183         desktopEntry.writeEntry("URL", url);
0184 
0185         desktopEntry.sync();
0186 
0187         url = QUrl::fromLocalFile(desktopFilePath);
0188         desktopFileCreated = true;
0189     }
0190 
0191     KPropertiesDialog *dialog = new KPropertiesDialog(url);
0192     dialog->setModal(false);
0193     dialog->setAttribute(Qt::WA_DeleteOnClose);
0194     dialog->show();
0195 
0196     connect(dialog, &KPropertiesDialog::accepted, this, [this, dialog, index, isPopup]() {
0197         QUrl url = dialog->url();
0198         QString path = url.toLocalFile();
0199 
0200         // If the user has renamed the file, make sure that the new
0201         // file name has the extension ".desktop".
0202         if (!path.endsWith(QLatin1String(".desktop"))) {
0203             QFile::rename(path, path + QLatin1String(".desktop"));
0204             path += QLatin1String(".desktop");
0205             url = QUrl::fromLocalFile(path);
0206         }
0207         Q_EMIT launcherEdited(url.toString(), index, isPopup);
0208     });
0209 
0210     connect(dialog, &KPropertiesDialog::rejected, this, [url, desktopFileCreated]() {
0211         if (desktopFileCreated) {
0212             // User didn't save the data, delete the temporary desktop file.
0213             QFile::remove(url.toLocalFile());
0214         }
0215     });
0216 }