File indexing completed on 2024-05-05 17:45:03
0001 /* 0002 SPDX-FileCopyrightText: 2007 Teemu Rytilahti <tpr@iki.fi> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "webshortcutrunner.h" 0008 0009 #include <KApplicationTrader> 0010 #include <KConfigGroup> 0011 #include <KIO/CommandLauncherJob> 0012 #include <KIO/OpenUrlJob> 0013 #include <KLocalizedString> 0014 #include <KRunner/RunnerManager> 0015 #include <KSharedConfig> 0016 #include <KShell> 0017 #include <KSycoca> 0018 #include <KUriFilter> 0019 #include <QAction> 0020 #include <QDBusConnection> 0021 0022 WebshortcutRunner::WebshortcutRunner(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) 0023 : Plasma::AbstractRunner(parent, metaData, args) 0024 , m_match(this) 0025 , m_filterBeforeRun(false) 0026 { 0027 setObjectName(QStringLiteral("Web Shortcut")); 0028 m_match.setType(Plasma::QueryMatch::ExactMatch); 0029 m_match.setRelevance(0.9); 0030 0031 // Listen for KUriFilter plugin config changes and update state... 0032 QDBusConnection sessionDbus = QDBusConnection::sessionBus(); 0033 sessionDbus.connect(QString(), QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure"), this, SLOT(loadSyntaxes())); 0034 loadSyntaxes(); 0035 configurePrivateBrowsingActions(); 0036 connect(KSycoca::self(), &KSycoca::databaseChanged, this, &WebshortcutRunner::configurePrivateBrowsingActions); 0037 setMinLetterCount(3); 0038 0039 connect(qobject_cast<Plasma::RunnerManager *>(parent), &Plasma::RunnerManager::queryFinished, this, [this]() { 0040 if (m_lastUsedContext.isValid() && !m_defaultKey.isEmpty() && m_lastUsedContext.matches().isEmpty()) { 0041 const QString queryWithDefaultProvider = m_defaultKey + m_delimiter + m_lastUsedContext.query(); 0042 KUriFilterData filterData(queryWithDefaultProvider); 0043 if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::WebShortcutFilter)) { 0044 m_match.setText(i18n("Search %1 for %2", filterData.searchProvider(), filterData.searchTerm())); 0045 m_match.setData(filterData.uri()); 0046 m_match.setIconName(filterData.iconName()); 0047 m_lastUsedContext.addMatch(m_match); 0048 } 0049 } 0050 }); 0051 } 0052 0053 WebshortcutRunner::~WebshortcutRunner() 0054 { 0055 } 0056 0057 void WebshortcutRunner::loadSyntaxes() 0058 { 0059 KUriFilterData filterData(QStringLiteral(":q")); 0060 filterData.setSearchFilteringOptions(KUriFilterData::RetrieveAvailableSearchProvidersOnly); 0061 if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) { 0062 m_delimiter = filterData.searchTermSeparator(); 0063 } 0064 m_regex = QRegularExpression(QStringLiteral("^([^ ]+)%1").arg(QRegularExpression::escape(m_delimiter))); 0065 0066 QList<Plasma::RunnerSyntax> syns; 0067 const QStringList providers = filterData.preferredSearchProviders(); 0068 const QRegularExpression replaceRegex(QStringLiteral(":q$")); 0069 const QString placeholder = QStringLiteral(":q:"); 0070 for (const QString &provider : providers) { 0071 Plasma::RunnerSyntax s(filterData.queryForPreferredSearchProvider(provider).replace(replaceRegex, placeholder), 0072 i18n("Opens \"%1\" in a web browser with the query :q:.", provider)); 0073 syns << s; 0074 } 0075 if (!providers.isEmpty()) { 0076 QString defaultKey = filterData.queryForSearchProvider(providers.constFirst()).defaultKey(); 0077 Plasma::RunnerSyntax s(QStringLiteral("!%1 :q:").arg(defaultKey), i18n("Search using the DuckDuckGo bang syntax")); 0078 syns << s; 0079 } 0080 0081 setSyntaxes(syns); 0082 m_lastFailedKey.clear(); 0083 m_lastProvider.clear(); 0084 m_lastKey.clear(); 0085 0086 // When we reload the syntaxes, our WebShortcut config has changed or is initialized 0087 const KConfigGroup grp = KSharedConfig::openConfig(QStringLiteral("kuriikwsfilterrc"))->group("General"); 0088 m_defaultKey = grp.readEntry("DefaultWebShortcut", QStringLiteral("duckduckgo")); 0089 } 0090 0091 void WebshortcutRunner::configurePrivateBrowsingActions() 0092 { 0093 qDeleteAll(m_match.actions()); 0094 m_match.setActions({}); 0095 const QString browserFile = KSharedConfig::openConfig(QStringLiteral("kdeglobals"))->group("General").readEntry("BrowserApplication"); 0096 KService::Ptr service; 0097 if (!browserFile.isEmpty()) { 0098 service = KService::serviceByStorageId(browserFile); 0099 } 0100 if (!service) { 0101 service = KApplicationTrader::preferredService(QStringLiteral("text/html")); 0102 } 0103 if (!service) { 0104 return; 0105 } 0106 const auto actions = service->actions(); 0107 for (const auto &action : actions) { 0108 bool containsPrivate = action.text().contains(QLatin1String("private"), Qt::CaseInsensitive); 0109 bool containsIncognito = action.text().contains(QLatin1String("incognito"), Qt::CaseInsensitive); 0110 if (containsPrivate || containsIncognito) { 0111 m_privateAction = action; 0112 const QString actionText = containsPrivate ? i18n("Search in private window") : i18n("Search in incognito window"); 0113 const QIcon icon = QIcon::fromTheme(QStringLiteral("view-private"), QIcon::fromTheme(QStringLiteral("view-hidden"))); 0114 m_match.setActions({new QAction(icon, actionText, this)}); 0115 return; 0116 } 0117 } 0118 } 0119 0120 void WebshortcutRunner::match(Plasma::RunnerContext &context) 0121 { 0122 m_lastUsedContext = context; 0123 const QString term = context.query(); 0124 const static QRegularExpression bangRegex(QStringLiteral("!([^ ]+).*")); 0125 const auto bangMatch = bangRegex.match(term); 0126 QString key; 0127 QString rawQuery = term; 0128 0129 if (bangMatch.hasMatch()) { 0130 key = bangMatch.captured(1); 0131 rawQuery = rawQuery.remove(rawQuery.indexOf(key) - 1, key.size() + 1); 0132 } else { 0133 const auto normalMatch = m_regex.match(term); 0134 if (normalMatch.hasMatch()) { 0135 key = normalMatch.captured(0); 0136 rawQuery = rawQuery.mid(key.length()); 0137 } 0138 } 0139 if (key.isEmpty() || key == m_lastFailedKey) { 0140 return; // we already know it's going to suck ;) 0141 } 0142 0143 // Do a fake user feedback text update if the keyword has not changed. 0144 // There is no point filtering the request on every key stroke. 0145 // filtering 0146 if (m_lastKey == key) { 0147 m_filterBeforeRun = true; 0148 m_match.setText(i18n("Search %1 for %2", m_lastProvider, rawQuery)); 0149 context.addMatch(m_match); 0150 return; 0151 } 0152 0153 KUriFilterData filterData(term); 0154 if (!KUriFilter::self()->filterSearchUri(filterData, KUriFilter::WebShortcutFilter)) { 0155 m_lastFailedKey = key; 0156 return; 0157 } 0158 0159 // Reuse key/provider for next matches. Other variables ca be reused, because the same match object is used 0160 m_lastKey = key; 0161 m_lastProvider = filterData.searchProvider(); 0162 m_match.setIconName(filterData.iconName()); 0163 m_match.setId(QStringLiteral("WebShortcut:") + key); 0164 0165 m_match.setText(i18n("Search %1 for %2", m_lastProvider, filterData.searchTerm())); 0166 m_match.setData(filterData.uri()); 0167 m_match.setUrls(QList<QUrl>{filterData.uri()}); 0168 context.addMatch(m_match); 0169 } 0170 0171 void WebshortcutRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) 0172 { 0173 QUrl location; 0174 if (m_filterBeforeRun) { 0175 m_filterBeforeRun = false; 0176 KUriFilterData filterData(context.query()); 0177 if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::WebShortcutFilter)) 0178 location = filterData.uri(); 0179 } else { 0180 location = match.data().toUrl(); 0181 } 0182 0183 if (!location.isEmpty()) { 0184 if (match.selectedAction()) { 0185 QString command; 0186 0187 // Chrome's exec line does not have a URL placeholder 0188 // Firefox's does, but only sometimes, depending on the distro 0189 // Replace placeholders if found, otherwise append at the end 0190 if (m_privateAction.exec().contains("%u")) { 0191 command = m_privateAction.exec().replace("%u", KShell::quoteArg(location.toString())); 0192 } else if (m_privateAction.exec().contains("%U")) { 0193 command = m_privateAction.exec().replace("%U", KShell::quoteArg(location.toString())); 0194 } else { 0195 command = m_privateAction.exec() + QLatin1Char(' ') + KShell::quoteArg(location.toString()); 0196 } 0197 0198 auto *job = new KIO::CommandLauncherJob(command); 0199 job->start(); 0200 } else { 0201 auto job = new KIO::OpenUrlJob(location); 0202 job->start(); 0203 } 0204 } 0205 } 0206 0207 K_PLUGIN_CLASS_WITH_JSON(WebshortcutRunner, "plasma-runner-webshortcuts.json") 0208 0209 #include "webshortcutrunner.moc"