File indexing completed on 2025-04-27 03:58:06

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2006-05-08
0007  * Description : Service menu operation methods
0008  *
0009  * SPDX-FileCopyrightText: 2014-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2006-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "dservicemenu.h"
0017 
0018 // Qt includes
0019 
0020 #include <QProcess>
0021 #include <QMimeType>
0022 #include <QMimeDatabase>
0023 #include <QStandardPaths>
0024 #include <QRegularExpression>
0025 
0026 // KDE includes
0027 
0028 #include <kservice_version.h>
0029 
0030 #if KSERVICE_VERSION > QT_VERSION_CHECK(5, 81, 0)
0031 #   include <kapplicationtrader.h>
0032 #else
0033 #   include <kmimetypetrader.h>
0034 #endif
0035 
0036 // Local includes
0037 
0038 #include "digikam_debug.h"
0039 #include "digikam_config.h"
0040 #include "digikam_globals.h"
0041 
0042 namespace Digikam
0043 {
0044 
0045 bool DServiceMenu::runFiles(const KService::Ptr& service,
0046                             const QList<QUrl>& urls)
0047 {
0048     return (runFiles(service->exec(), urls, service));
0049 }
0050 
0051 bool DServiceMenu::runFiles(const QString& appCmd,
0052                             const QList<QUrl>& urls,
0053                             const KService::Ptr& service)
0054 {
0055     QRegularExpression split(QLatin1String(" +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"));
0056     QStringList cmdList = appCmd.split(split, QT_SKIP_EMPTY_PARTS);
0057     QList<QUrl> urlList = urls;
0058 
0059     if (cmdList.isEmpty() || urlList.isEmpty())
0060     {
0061         return false;
0062     }
0063 
0064     if (!appCmd.contains(QLatin1String("%f"), Qt::CaseInsensitive) &&
0065         !appCmd.contains(QLatin1String("%u"), Qt::CaseInsensitive) &&
0066         !appCmd.contains(QLatin1String("%d"), Qt::CaseInsensitive))
0067     {
0068         cmdList << QLatin1String("%f");
0069     }
0070 
0071     QString exec;
0072     QString name;
0073     QString icon;
0074     QString term;
0075 
0076     QStringList dirs;
0077     QStringList files;
0078     QStringList cmdArgs;
0079     QStringList termOpts;
0080 
0081     bool useTerminal = false;
0082     bool openNewRun  = false;
0083 
0084     if (service)
0085     {
0086         name = service->desktopEntryName();
0087         icon = service->icon();
0088 
0089 #ifdef Q_OS_LINUX
0090 
0091         if (service->terminal())
0092         {
0093             termOpts = service->terminalOptions().split(split, QT_SKIP_EMPTY_PARTS);
0094             term     = QStandardPaths::findExecutable(QLatin1String("konsole"));
0095 
0096             if (term.isEmpty())
0097             {
0098                 term = QStandardPaths::findExecutable(QLatin1String("xterm"));
0099                 termOpts.replaceInStrings(QLatin1String("--noclose"),
0100                                           QLatin1String("-hold"));
0101             }
0102 
0103             useTerminal = !term.isEmpty();
0104         }
0105 
0106 #endif // Q_OS_LINUX
0107 
0108     }
0109 
0110     QProcess* const process = new QProcess();
0111     QProcessEnvironment env = adjustedEnvironmentForAppImage();
0112 
0113     Q_FOREACH (const QUrl& url, urlList)
0114     {
0115         dirs  << url.adjusted(QUrl::RemoveFilename).toLocalFile();
0116         files << url.toLocalFile();
0117     }
0118 
0119     Q_FOREACH (const QString& cmdString, cmdList)
0120     {
0121         QString cmd = cmdString;
0122 
0123         if (cmd.startsWith(QLatin1Char('"')) && cmd.endsWith(QLatin1Char('"')))
0124         {
0125             cmd.remove(0, 1).chop(1);
0126         }
0127 
0128         if (exec.isEmpty() && cmd.contains(QLatin1Char('=')))
0129         {
0130             QStringList envList = cmd.split(QLatin1Char('='), QT_SKIP_EMPTY_PARTS);
0131 
0132             if (envList.count() == 2)
0133             {
0134                 env.insert(envList[0], envList[1]);
0135             }
0136 
0137             continue;
0138         }
0139         else if (exec.isEmpty())
0140         {
0141             exec = cmd;
0142             continue;
0143         }
0144 
0145         if      (cmd == QLatin1String("%c"))
0146         {
0147             cmdArgs << name;
0148         }
0149         else if (cmd == QLatin1String("%i"))
0150         {
0151             cmdArgs << icon;
0152         }
0153         else if (cmd == QLatin1String("%f"))
0154         {
0155             cmdArgs << files.first();
0156             openNewRun = true;
0157         }
0158         else if (cmd == QLatin1String("%F"))
0159         {
0160             cmdArgs << files;
0161         }
0162         else if (cmd == QLatin1String("%u"))
0163         {
0164             cmdArgs << files.first();
0165             openNewRun = true;
0166         }
0167         else if (cmd == QLatin1String("%U"))
0168         {
0169             cmdArgs << files;
0170         }
0171         else if (cmd == QLatin1String("%d"))
0172         {
0173             cmdArgs << dirs.first();
0174             openNewRun = true;
0175         }
0176         else if (cmd == QLatin1String("%D"))
0177         {
0178             cmdArgs << dirs;
0179         }
0180         else
0181         {
0182             cmdArgs << cmd;
0183         }
0184     }
0185 
0186     process->setProcessEnvironment(env);
0187 
0188     if (useTerminal)
0189     {
0190         termOpts << QLatin1String("-e") << exec << cmdArgs;
0191         process->start(term, termOpts);
0192     }
0193     else
0194     {
0195         process->start(exec, cmdArgs);
0196     }
0197 
0198     bool ret = true;
0199     ret     &= process->waitForStarted();
0200 
0201     if (openNewRun)
0202     {
0203         urlList.removeFirst();
0204 
0205         if (!urlList.isEmpty())
0206         {
0207             ret &= runFiles(appCmd, urlList, service);
0208         }
0209     }
0210 
0211     return ret;
0212 }
0213 
0214 KService::List DServiceMenu::servicesForOpenWith(const QList<QUrl>& urls)
0215 {
0216     // This code is inspired by KonqMenuActions:
0217     // kdebase/apps/lib/konq/konq_menuactions.cpp
0218 
0219     QStringList    mimeTypes;
0220     KService::List offers;
0221 
0222     Q_FOREACH (const QUrl& item, urls)
0223     {
0224         const QString mimeType = QMimeDatabase().mimeTypeForFile(item.toLocalFile(),
0225                                                                  QMimeDatabase::MatchExtension).name();
0226 
0227         if (!mimeTypes.contains(mimeType))
0228         {
0229             mimeTypes << mimeType;
0230         }
0231     }
0232 
0233     if (!mimeTypes.isEmpty())
0234     {
0235         // Query trader
0236 
0237         const QString firstMimeType      = mimeTypes.takeFirst();
0238         const QString constraintTemplate = QLatin1String("'%1' in ServiceTypes");
0239         QStringList constraints;
0240 
0241         Q_FOREACH (const QString& mimeType, mimeTypes)
0242         {
0243             constraints << constraintTemplate.arg(mimeType);
0244         }
0245 
0246 #if KSERVICE_VERSION > QT_VERSION_CHECK(5, 81, 0)
0247         offers = KApplicationTrader::queryByMimeType(firstMimeType);
0248 #else
0249         offers = KMimeTypeTrader::self()->query(firstMimeType,
0250                                                 QLatin1String("Application"),
0251                                                 constraints.join(QLatin1String(" and ")));
0252 #endif
0253 
0254         // remove duplicate service entries
0255 
0256         QSet<QString> seenApps;
0257 
0258         for (KService::List::iterator it = offers.begin() ; it != offers.end() ; )
0259         {
0260             const QString appName((*it)->name());
0261 
0262             if (!seenApps.contains(appName))
0263             {
0264                 seenApps.insert(appName);
0265                 ++it;
0266             }
0267             else
0268             {
0269                 it = offers.erase(it);
0270             }
0271         }
0272     }
0273 
0274     return offers;
0275 }
0276 
0277 } // namespace Digikam