File indexing completed on 2024-06-16 05:08:35

0001 /*
0002     SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Self
0008 #include "applicationlistmodel.h"
0009 
0010 // Qt
0011 #include <QByteArray>
0012 #include <QDebug>
0013 #include <QModelIndex>
0014 #include <QProcess>
0015 #include <QRegularExpression>
0016 
0017 // KDE
0018 #include <KConfigGroup>
0019 #include <KIO/ApplicationLauncherJob>
0020 #include <KNotificationJobUiDelegate>
0021 #include <KService>
0022 #include <KServiceGroup>
0023 #include <KSharedConfig>
0024 #include <KShell>
0025 #include <KSycoca>
0026 #include <KSycocaEntry>
0027 #include <PlasmaActivities/ResourceInstance>
0028 
0029 ApplicationListModel::ApplicationListModel(QObject *parent)
0030     : QAbstractListModel(parent)
0031 {
0032     connect(KSycoca::self(), static_cast<void (KSycoca::*)()>(&KSycoca::databaseChanged), this, &ApplicationListModel::sycocaDbChanged);
0033 }
0034 
0035 ApplicationListModel::~ApplicationListModel() = default;
0036 
0037 QHash<int, QByteArray> ApplicationListModel::roleNames() const
0038 {
0039     QHash<int, QByteArray> roleNames;
0040     roleNames[ApplicationNameRole] = "ApplicationNameRole";
0041     roleNames[ApplicationCommentRole] = "ApplicationCommentRole";
0042     roleNames[ApplicationIconRole] = "ApplicationIconRole";
0043     roleNames[ApplicationCategoriesRole] = "ApplicationCategoriesRole";
0044     roleNames[ApplicationStorageIdRole] = "ApplicationStorageIdRole";
0045     roleNames[ApplicationEntryPathRole] = "ApplicationEntryPathRole";
0046     roleNames[ApplicationDesktopRole] = "ApplicationDesktopRole";
0047     roleNames[ApplicationStartupNotifyRole] = "ApplicationStartupNotifyRole";
0048     roleNames[ApplicationOriginalRowRole] = "ApplicationOriginalRowRole";
0049 
0050     return roleNames;
0051 }
0052 
0053 void ApplicationListModel::sycocaDbChanged()
0054 {
0055     m_applicationList.clear();
0056     m_voiceAppSkills.clear();
0057 
0058     loadApplications();
0059 }
0060 
0061 bool appNameLessThan(const ApplicationData &a1, const ApplicationData &a2)
0062 {
0063     return a1.name.toLower() < a2.name.toLower();
0064 }
0065 
0066 QStringList ApplicationListModel::voiceAppSkills() const
0067 {
0068     return m_voiceAppSkills;
0069 }
0070 
0071 void ApplicationListModel::loadApplications()
0072 {
0073     auto cfg = KSharedConfig::openConfig("applications-blacklistrc");
0074     auto blgroup = KConfigGroup(cfg, QStringLiteral("Applications"));
0075 
0076     // This is only temporary to get a clue what those apps' desktop files are called
0077     // I'll remove it once I've done a blacklist
0078     QStringList bl;
0079 
0080     QStringList blacklist = blgroup.readEntry("blacklist", QStringList());
0081 
0082     beginResetModel();
0083 
0084     m_applicationList.clear();
0085 
0086     KServiceGroup::Ptr group = KServiceGroup::root();
0087     if (!group || !group->isValid()) {
0088         return;
0089     }
0090     KServiceGroup::List subGroupList = group->entries(true);
0091 
0092     QMap<int, ApplicationData> orderedList;
0093     QList<ApplicationData> unorderedList;
0094 
0095     // Iterate over all entries in the group
0096     while (!subGroupList.isEmpty()) {
0097         KSycocaEntry::Ptr groupEntry = subGroupList.first();
0098         subGroupList.pop_front();
0099 
0100         if (groupEntry->isType(KST_KServiceGroup)) {
0101             KServiceGroup::Ptr serviceGroup(static_cast<KServiceGroup *>(groupEntry.data()));
0102 
0103             if (!serviceGroup->noDisplay()) {
0104                 KServiceGroup::List entryGroupList = serviceGroup->entries(true);
0105 
0106                 for (KServiceGroup::List::ConstIterator it = entryGroupList.constBegin(); it != entryGroupList.constEnd(); it++) {
0107                     KSycocaEntry::Ptr entry = (*it);
0108 
0109                     if (entry->isType(KST_KServiceGroup)) {
0110                         KServiceGroup::Ptr serviceGroup(static_cast<KServiceGroup *>(entry.data()));
0111                         subGroupList << serviceGroup;
0112 
0113                     } else if (const auto service = static_cast<KService *>(entry.data()); entry->isType(KST_KService) && !service->exec().isEmpty()) {
0114                         qDebug() << service->property<QStringList>("Categories");
0115                         qDebug() << " desktopEntryName: " << service->desktopEntryName();
0116 
0117                         // else if (entry->property("Exec").isValid()) {
0118                         //  KService::Ptr service(static_cast<KService* >(entry.data()));
0119 
0120                         //  qDebug() << " desktopEntryName: " << service->desktopEntryName();
0121 
0122                         if (service->isApplication() && !blacklist.contains(service->desktopEntryName()) && service->showOnCurrentPlatform()
0123                             && !service->property<bool>("Terminal")) {
0124                             QRegularExpression voiceExpr(QStringLiteral("mycroft-gui-app .* --skill=(.*)\\.home"));
0125 
0126                             if (service->categories().contains(QStringLiteral("VoiceApp")) && voiceExpr.match(service->exec()).hasMatch()) {
0127                                 QString exec = service->exec();
0128                                 exec.replace(voiceExpr, QStringLiteral("\\1"));
0129                                 if (!exec.isEmpty()) {
0130                                     m_voiceAppSkills << exec;
0131                                 }
0132                             }
0133 
0134                             bl << service->desktopEntryName();
0135 
0136                             ApplicationData data;
0137                             data.name = service->name();
0138                             data.comment = service->comment();
0139                             data.icon = service->icon();
0140                             data.categories = service->categories();
0141                             data.storageId = service->storageId();
0142                             data.entryPath = service->exec();
0143                             data.desktopPath = service->entryPath();
0144                             data.startupNotify = service->property<bool>("StartupNotify");
0145 
0146                             auto it = m_appPositions.constFind(service->storageId());
0147                             if (it != m_appPositions.constEnd()) {
0148                                 orderedList[*it] = data;
0149                             } else {
0150                                 unorderedList << data;
0151                             }
0152                         }
0153                     }
0154                 }
0155             }
0156         }
0157     }
0158 
0159     Q_EMIT voiceAppSkillsChanged();
0160 
0161     blgroup.writeEntry("allapps", bl);
0162     blgroup.writeEntry("blacklist", blacklist);
0163     cfg->sync();
0164 
0165     std::sort(unorderedList.begin(), unorderedList.end(), appNameLessThan);
0166     m_applicationList << orderedList.values();
0167     m_applicationList << unorderedList;
0168 
0169     endResetModel();
0170     Q_EMIT countChanged();
0171 }
0172 
0173 QVariant ApplicationListModel::data(const QModelIndex &index, int role) const
0174 {
0175     if (!index.isValid()) {
0176         return QVariant();
0177     }
0178 
0179     switch (role) {
0180     case Qt::DisplayRole:
0181     case ApplicationNameRole:
0182         return m_applicationList.at(index.row()).name;
0183     case ApplicationCommentRole:
0184         return m_applicationList.at(index.row()).comment;
0185     case ApplicationIconRole:
0186         return m_applicationList.at(index.row()).icon;
0187     case ApplicationCategoriesRole:
0188         return m_applicationList.at(index.row()).categories;
0189     case ApplicationStorageIdRole:
0190         return m_applicationList.at(index.row()).storageId;
0191     case ApplicationEntryPathRole:
0192         return m_applicationList.at(index.row()).entryPath;
0193     case ApplicationDesktopRole:
0194         return m_applicationList.at(index.row()).desktopPath;
0195     case ApplicationStartupNotifyRole:
0196         return m_applicationList.at(index.row()).startupNotify;
0197     case ApplicationOriginalRowRole:
0198         return index.row();
0199 
0200     default:
0201         return QVariant();
0202     }
0203 }
0204 
0205 Qt::ItemFlags ApplicationListModel::flags(const QModelIndex &index) const
0206 {
0207     if (!index.isValid()) {
0208         return Qt::NoItemFlags;
0209     }
0210     return Qt::ItemIsDragEnabled | QAbstractItemModel::flags(index);
0211 }
0212 
0213 int ApplicationListModel::rowCount(const QModelIndex &parent) const
0214 {
0215     if (parent.isValid()) {
0216         return 0;
0217     }
0218 
0219     return m_applicationList.count();
0220 }
0221 
0222 void ApplicationListModel::moveRow(const QModelIndex & /* sourceParent */, int sourceRow, const QModelIndex & /* destinationParent */, int destinationChild)
0223 {
0224     moveItem(sourceRow, destinationChild);
0225 }
0226 
0227 Q_INVOKABLE void ApplicationListModel::moveItem(int row, int destination)
0228 {
0229     if (row < 0 || destination < 0 || row >= m_applicationList.length() || destination >= m_applicationList.length() || row == destination) {
0230         return;
0231     }
0232     if (destination > row) {
0233         ++destination;
0234     }
0235 
0236     beginMoveRows(QModelIndex(), row, row, QModelIndex(), destination);
0237     if (destination > row) {
0238         ApplicationData data = m_applicationList.at(row);
0239         m_applicationList.insert(destination, data);
0240         m_applicationList.takeAt(row);
0241     } else {
0242         ApplicationData data = m_applicationList.takeAt(row);
0243         m_applicationList.insert(destination, data);
0244     }
0245 
0246     m_appOrder.clear();
0247     m_appPositions.clear();
0248     int i = 0;
0249     for (const auto &app : std::as_const(m_applicationList)) {
0250         m_appOrder << app.storageId;
0251         m_appPositions[app.storageId] = i;
0252         ++i;
0253     }
0254 
0255     Q_EMIT appOrderChanged();
0256     endMoveRows();
0257 }
0258 
0259 void ApplicationListModel::executeCommand(const QString &command)
0260 {
0261     qWarning() << "Executing" << command;
0262     QStringList args = command.split(QStringLiteral(" "));
0263     QString app = args.takeFirst();
0264     QProcess::startDetached(app, args);
0265 }
0266 
0267 void ApplicationListModel::runApplication(const QString &storageId)
0268 {
0269     if (storageId.isEmpty()) {
0270         return;
0271     }
0272 
0273     KService::Ptr service = KService::serviceByStorageId(storageId);
0274 
0275     // KRun::runApplication(*service, QList<QUrl>(), nullptr);
0276     KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service);
0277     job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled));
0278     job->start();
0279 
0280     KActivities::ResourceInstance::notifyAccessed(QUrl(QStringLiteral("applications:") + service->storageId()), QStringLiteral("org.kde.plasma.kicker"));
0281 }
0282 
0283 QStringList ApplicationListModel::appOrder() const
0284 {
0285     return m_appOrder;
0286 }
0287 
0288 void ApplicationListModel::setAppOrder(const QStringList &order)
0289 {
0290     if (m_appOrder == order) {
0291         return;
0292     }
0293 
0294     m_appOrder = order;
0295     m_appPositions.clear();
0296     int i = 0;
0297     for (const auto &app : std::as_const(m_appOrder)) {
0298         m_appPositions[app] = i;
0299         ++i;
0300     }
0301     Q_EMIT appOrderChanged();
0302 }