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 }