File indexing completed on 2024-05-12 05:35:38

0001 /*
0002     SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
0003     SPDX-FileCopyrightText: 2020 Méven Car <meven.car@kdemail.net>
0004     SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
0005     SPDX-FileCopyrightText: 2022 Méven Car <meven@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "componentchooser.h"
0011 
0012 #include <QApplication>
0013 #include <QDBusConnection>
0014 #include <QDBusMessage>
0015 #include <QMimeDatabase>
0016 
0017 #include <KApplicationTrader>
0018 #include <KBuildSycocaProgressDialog>
0019 #include <KConfigGroup>
0020 #include <KOpenWithDialog>
0021 #include <KQuickConfigModule>
0022 #include <KService>
0023 #include <KSharedConfig>
0024 
0025 extern KSERVICE_EXPORT int ksycoca_ms_between_checks;
0026 
0027 ComponentChooser::ComponentChooser(QObject *parent,
0028                                    const QString &mimeType,
0029                                    const QString &applicationCategory,
0030                                    const QString &defaultApplication,
0031                                    const QString &dialogText)
0032     : QObject(parent)
0033     , m_mimeType(mimeType)
0034     , m_applicationCategory(applicationCategory)
0035     , m_defaultApplication(defaultApplication)
0036     , m_dialogText(dialogText)
0037 {
0038     qRegisterMetaType<QList<PairQml>>("QList<PairQml>");
0039 
0040     m_model = new ApplicationModel(this);
0041     connect(m_model, &QAbstractItemModel::modelReset, this, &ComponentChooser::modelChanged);
0042 }
0043 
0044 void ComponentChooser::defaults()
0045 {
0046     const auto defaultIndex = m_model->defaultIndex();
0047     if (defaultIndex) {
0048         select(*defaultIndex);
0049     }
0050 }
0051 
0052 void ComponentChooser::load()
0053 {
0054     m_model->load(m_mimeType, m_applicationCategory, m_defaultApplication, KApplicationTrader::preferredService(m_mimeType));
0055 
0056     m_index = m_model->currentIndex();
0057 
0058     m_currentApplication = currentStorageId();
0059 
0060     Q_EMIT indexChanged();
0061     Q_EMIT isDefaultsChanged();
0062 }
0063 
0064 void ComponentChooser::select(int index)
0065 {
0066     // Other selection
0067     if (index == m_model->rowCount() - 1) {
0068         KOpenWithDialog *dialog = new KOpenWithDialog(QList<QUrl>(), m_mimeType, m_dialogText, QString(), QApplication::activeWindow());
0069         dialog->setSaveNewApplications(true);
0070         dialog->setAttribute(Qt::WA_DeleteOnClose);
0071         connect(dialog, &KOpenWithDialog::finished, this, [this, dialog](int result) {
0072             if (result == QDialog::Rejected) {
0073                 Q_EMIT indexChanged();
0074                 Q_EMIT isDefaultsChanged();
0075                 return;
0076             }
0077 
0078             const auto serviceStorageId = dialog->service()->storageId();
0079 
0080             // Check if the selected application is already in the list
0081             QModelIndex modelIndex = m_model->findByStorageId(serviceStorageId);
0082             if (modelIndex.isValid()) {
0083                 select(modelIndex.row());
0084                 return;
0085             }
0086 
0087             auto newIndex = m_model->addApplicationBeforeLast(dialog->service());
0088             select(newIndex);
0089         });
0090         dialog->open();
0091     } else {
0092         m_index = index;
0093 
0094         const auto selectedIndex = m_model->index(index, 0);
0095         m_model->setData(selectedIndex, true, ApplicationModel::Selected);
0096 
0097         Q_EMIT indexChanged();
0098         Q_EMIT isDefaultsChanged();
0099     }
0100 }
0101 
0102 void ComponentChooser::saveMimeTypeAssociations(const QString &storageId, const QStringList &mimeTypes, bool forceUnsupportedMimeType)
0103 {
0104     if (storageId.isEmpty()) {
0105         return;
0106     }
0107 
0108     // This grabs the configuration from mimeapps.list, which is DE agnostic and part of the XDG standard.
0109     KSharedConfig::Ptr mimeAppsList = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation);
0110 
0111     if (mimeAppsList->isConfigWritable(true)) {
0112         const auto appService = KService::serviceByStorageId(storageId);
0113 
0114         for (const QString &mimeType : mimeTypes) {
0115             if (!forceUnsupportedMimeType && appService && !serviceSupportsMimeType(appService, mimeType)) {
0116                 // skip mimetype association if the app does not support it at all
0117                 continue;
0118             }
0119 
0120             KApplicationTrader::setPreferredService(mimeType, appService);
0121         }
0122 
0123         m_currentApplication = storageId;
0124     }
0125 }
0126 
0127 void ComponentChooser::onSaved()
0128 {
0129     Q_EMIT indexChanged();
0130     Q_EMIT isDefaultsChanged();
0131 }
0132 
0133 QStringList ComponentChooser::unsupportedMimeTypes() const
0134 {
0135     const auto preferredApp = currentStorageId();
0136 
0137     if (preferredApp.isEmpty()) {
0138         return QStringList{};
0139     }
0140 
0141     const auto db = QMimeDatabase();
0142 
0143     QStringList unsupportedMimeTypes;
0144 
0145     const auto appService = KService::serviceByStorageId(preferredApp);
0146     const auto componentMimeTypes = mimeTypes();
0147     for (const QString &mimeType : componentMimeTypes) {
0148         if (!serviceSupportsMimeType(appService, mimeType)) {
0149             const auto preferredService = KApplicationTrader::preferredService(mimeType);
0150             if (!preferredService || (preferredService->storageId() != appService->storageId())) {
0151                 unsupportedMimeTypes << mimeType;
0152             }
0153         }
0154     }
0155 
0156     return unsupportedMimeTypes;
0157 }
0158 
0159 void ComponentChooser::saveAssociationUnsuportedMimeTypes()
0160 {
0161     const auto storageId = currentStorageId();
0162 
0163     saveMimeTypeAssociations(storageId, unsupportedMimeTypes(), true);
0164     forceReloadServiceCache();
0165     load();
0166 }
0167 
0168 QList<PairQml> ComponentChooser::mimeTypesNotAssociated() const
0169 {
0170     auto ret = QList<PairQml>();
0171 
0172     const auto db = QMimeDatabase();
0173     const auto storageId = m_currentApplication;
0174 
0175     const auto appService = KService::serviceByStorageId(storageId);
0176 
0177     if (!appService) {
0178         return ret;
0179     }
0180 
0181     const auto mimes = mimeTypes();
0182 
0183     for (const QString &mimeType : mimes) {
0184         const auto service = KApplicationTrader::preferredService(mimeType);
0185 
0186         if (service && service->storageId() != storageId &&
0187             // only explicitly supported mimetype
0188             serviceSupportsMimeType(appService, mimeType)) {
0189             ret.append(PairQml(service->name(), mimeType));
0190         }
0191     }
0192 
0193     return ret;
0194 }
0195 
0196 void ComponentChooser::saveMimeTypesNotAssociated()
0197 {
0198     const auto mimeTypeNotAssociated = mimeTypesNotAssociated();
0199 
0200     auto mimeTypeList = QList<QString>();
0201     for (const auto &pair : mimeTypeNotAssociated) {
0202         mimeTypeList.append(pair.second.toString());
0203     }
0204 
0205     const auto storageId = currentStorageId();
0206     saveMimeTypeAssociations(storageId, mimeTypeList);
0207 
0208     forceReloadServiceCache();
0209     load();
0210 }
0211 
0212 QString ComponentChooser::currentStorageId() const
0213 {
0214     return m_model->data(m_index, ApplicationModel::StorageId).toString();
0215 }
0216 
0217 QString ComponentChooser::applicationName() const
0218 {
0219     return m_model->data(m_index, Qt::DisplayRole).toString();
0220 }
0221 
0222 QString ComponentChooser::applicationIcon() const
0223 {
0224     return m_model->data(m_index, ApplicationModel::Icon).toString();
0225 }
0226 
0227 QStringList ComponentChooser::mimeTypes() const
0228 {
0229     return QStringList{};
0230 }
0231 
0232 void ComponentChooser::forceReloadServiceCache()
0233 {
0234     KBuildSycocaProgressDialog::rebuildKSycoca(QApplication::activeWindow());
0235 
0236     // HACK to ensure mime cache is updated right away
0237     int previous_delay = ksycoca_ms_between_checks;
0238     ksycoca_ms_between_checks = 0;
0239     KService::allServices();
0240     ksycoca_ms_between_checks = previous_delay;
0241 }
0242 
0243 void ComponentChooser::save()
0244 {
0245     // default impl for simple application kinds
0246     const auto storageId = currentStorageId();
0247 
0248     saveMimeTypeAssociations(storageId, mimeTypes());
0249 }
0250 
0251 bool ComponentChooser::isDefaults() const
0252 {
0253     const auto defaultIndex = m_model->defaultIndex();
0254     return !defaultIndex.has_value() || *defaultIndex == m_index;
0255 }
0256 
0257 bool ComponentChooser::isSaveNeeded() const
0258 {
0259     const auto storageId = currentStorageId();
0260     return m_model->rowCount() > 1 && (m_currentApplication != storageId) && storageId != "";
0261 }
0262 
0263 bool ComponentChooser::serviceSupportsMimeType(KService::Ptr service, const QString &targetMimeType)
0264 {
0265     static QMimeDatabase db;
0266     const QStringList mimeTypes = service->mimeTypes();
0267     for (const QString &supportedMimeType : mimeTypes) {
0268         if (supportedMimeType == targetMimeType) {
0269             return true;
0270         }
0271 
0272         if (db.mimeTypeForName(targetMimeType).inherits(supportedMimeType)) {
0273             return true;
0274         }
0275     }
0276 
0277     if (targetMimeType.startsWith(QLatin1String("x-scheme-handler/")) && service->schemeHandlers().contains(targetMimeType.mid(17))) {
0278         return true;
0279     }
0280 
0281     return false;
0282 }