diff --git a/CMakeLists.txt b/CMakeLists.txt index e18573d..7c2a6c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,102 +1,109 @@ cmake_minimum_required(VERSION 3.0) set(KF5_VERSION "5.51.0") # handled by release scripts set(KF5_DEP_VERSION "5.50.0") # handled by release scripts project(KRunner VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) find_package(ECM 5.50.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMAddQch) include(CMakePackageConfigHelpers) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(KDEPackageAppTemplates) include(ECMQtDeclareLoggingCategory) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version(PROJECT VARIABLE_PREFIX KRUNNER VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/krunner_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5RunnerConfigVersion.cmake" ) # Dependencies set(REQUIRED_QT_VERSION 5.8.0) find_package(Qt5 ${REQUIRED_QT_VERSION} NO_MODULE REQUIRED Gui Widgets Quick) find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5CoreAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5I18n ${KF5_DEP_VERSION} REQUIRED) find_package(KF5KIO ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Service ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Plasma ${KF5_DEP_VERSION} REQUIRED) find_package(KF5ThreadWeaver ${KF5_DEP_VERSION} REQUIRED) set(KRunner_AUTOMOC_MACRO_NAMES "K_EXPORT_PLASMA_RUNNER" "K_EXPORT_RUNNER_CONFIG") if(NOT CMAKE_VERSION VERSION_LESS "3.10.0") # CMake 3.9+ warns about automoc on files without Q_OBJECT, and doesn't know about other macros. # 3.10+ lets us provide more macro names that require automoc. list(APPEND CMAKE_AUTOMOC_MACRO_NAMES ${KRunner_AUTOMOC_MACRO_NAMES}) endif() +add_definitions(-DQT_NO_CAST_FROM_ASCII) +add_definitions(-DQT_NO_CAST_TO_ASCII) +add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) +add_definitions(-DQT_NO_URL_CAST_FROM_STRING) +add_definitions(-DQT_USE_QSTRINGBUILDER) +#add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) + # Subdirectories add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() add_subdirectory(templates) # Create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KF5Runner") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5Runner_QCH FILE KF5RunnerQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5RunnerQchTargets.cmake\")") endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5RunnerConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5RunnerConfig.cmake" INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5RunnerConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5RunnerConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel) install(EXPORT KF5RunnerTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5RunnerTargets.cmake NAMESPACE KF5::) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/krunner_version.h" DESTINATION "${KF5_INCLUDE_INSTALL_DIR}" COMPONENT Devel) # contains list of debug categories, for kdebugsettings install(FILES krunner.categories DESTINATION ${KDE_INSTALL_CONFDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/dbusrunnertest.cpp b/autotests/dbusrunnertest.cpp index d3b6396..d97b73d 100644 --- a/autotests/dbusrunnertest.cpp +++ b/autotests/dbusrunnertest.cpp @@ -1,181 +1,181 @@ /* * Copyright (C) 2017 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2 as * published by the Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "runnermanager.h" #include #include #include #include using namespace Plasma; Q_DECLARE_METATYPE(Plasma::QueryMatch); Q_DECLARE_METATYPE(QList); class DBusRunnerTest : public QObject { Q_OBJECT public: DBusRunnerTest(); ~DBusRunnerTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testMatch(); void testMulti(); private: QStringList m_filesForCleanup; }; DBusRunnerTest::DBusRunnerTest() : QObject() { qRegisterMetaType >(); } DBusRunnerTest::~DBusRunnerTest() { } void DBusRunnerTest::initTestCase() { // Set up a layer in the bin dir so ksycoca finds the Plasma/Runner service type const QByteArray defaultDataDirs = qEnvironmentVariableIsSet("XDG_DATA_DIRS") ? qgetenv("XDG_DATA_DIRS") : QByteArray("/usr/local:/usr"); - const QByteArray modifiedDataDirs = QFile::encodeName(QCoreApplication::applicationDirPath()) + "/data:" + defaultDataDirs; + const QByteArray modifiedDataDirs = QFile::encodeName(QCoreApplication::applicationDirPath()) + QByteArrayLiteral("/data:") + defaultDataDirs; qputenv("XDG_DATA_DIRS", modifiedDataDirs); QStandardPaths::setTestModeEnabled(true); QDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)).mkpath(QStringLiteral("kservices5")); { const QString fakeServicePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kservices5/dbusrunnertest.desktop"); QFile::copy(QFINDTESTDATA("dbusrunnertest.desktop"), fakeServicePath); m_filesForCleanup << fakeServicePath; } { const QString fakeServicePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kservices5/dbusrunnertestmulti.desktop"); QFile::copy(QFINDTESTDATA("dbusrunnertestmulti.desktop"), fakeServicePath); m_filesForCleanup << fakeServicePath; } KSycoca::self()->ensureCacheValid(); } void DBusRunnerTest::cleanupTestCase() { for(const QString path: m_filesForCleanup) { QFile::remove(path); } } void DBusRunnerTest::testMatch() { QProcess process; - process.start(QFINDTESTDATA("testremoterunner"), QStringList({"net.krunnertests.dave"})); + process.start(QFINDTESTDATA("testremoterunner"), QStringList({QStringLiteral("net.krunnertests.dave")})); QVERIFY(process.waitForStarted()); QTest::qSleep(500); RunnerManager m; auto s = KService::serviceByDesktopPath(QStringLiteral("dbusrunnertest.desktop")); QVERIFY(s); m.loadRunner(s); m.launchQuery(QStringLiteral("foo")); QSignalSpy spy(&m, &RunnerManager::matchesChanged); QVERIFY(spy.wait()); //verify matches QCOMPARE(m.matches().count(), 1); auto result = m.matches().first(); //see testremoterunner.cpp QCOMPARE(result.id(), QStringLiteral("dbusrunnertest_id1")); //note the runner name is prepended QCOMPARE(result.text(), QStringLiteral("Match 1")); QCOMPARE(result.iconName(), QStringLiteral("icon1")); QCOMPARE(result.type(), Plasma::QueryMatch::ExactMatch); //relevance can't be compared easily because RunnerContext meddles with it //verify actions auto actions = m.actionsForMatch(result); QCOMPARE(actions.count(), 1); auto action = actions.first(); QCOMPARE(action->text(), QStringLiteral("Action 1")); QSignalSpy processSpy(&process, &QProcess::readyRead); m.run(result); processSpy.wait(); QCOMPARE(process.readAllStandardOutput().trimmed(), QByteArray("Running:id1:")); result.setSelectedAction(action); m.run(result); processSpy.wait(); QCOMPARE(process.readAllStandardOutput().trimmed(), QByteArray("Running:id1:action1")); process.kill(); process.waitForFinished(); } void DBusRunnerTest::testMulti() { QProcess process1; - process1.start(QFINDTESTDATA("testremoterunner"), QStringList({"net.krunnertests.multi.a1"})); + process1.start(QFINDTESTDATA("testremoterunner"), QStringList({QStringLiteral("net.krunnertests.multi.a1")})); QVERIFY(process1.waitForStarted()); QProcess process2; - process2.start(QFINDTESTDATA("testremoterunner"), QStringList({"net.krunnertests.multi.a2"})); + process2.start(QFINDTESTDATA("testremoterunner"), QStringList({QStringLiteral("net.krunnertests.multi.a2")})); QVERIFY(process2.waitForStarted()); QTest::qSleep(500); RunnerManager m; auto s = KService::serviceByDesktopPath(QStringLiteral("dbusrunnertestmulti.desktop")); QVERIFY(s); m.loadRunner(s); m.launchQuery(QStringLiteral("foo")); QSignalSpy spy(&m, &RunnerManager::matchesChanged); QVERIFY(spy.wait()); //verify matches, must be one from each QCOMPARE(m.matches().count(), 2); QString first = m.matches().at(0).data().toString(); QString second = m.matches().at(1).data().toString(); QVERIFY(first != second); - QVERIFY(first == "net.krunnertests.multi.a1" || first == "net.krunnertests.multi.a2"); - QVERIFY(second == "net.krunnertests.multi.a1" || second == "net.krunnertests.multi.a2"); + QVERIFY(first == QStringLiteral("net.krunnertests.multi.a1") || first == QStringLiteral("net.krunnertests.multi.a2")); + QVERIFY(second == QStringLiteral("net.krunnertests.multi.a1") || second == QStringLiteral("net.krunnertests.multi.a2")); process1.kill(); process2.kill(); process1.waitForFinished(); process2.waitForFinished(); } QTEST_MAIN(DBusRunnerTest) #include "dbusrunnertest.moc" diff --git a/src/abstractrunner.cpp b/src/abstractrunner.cpp index d10b366..e1bb9c3 100644 --- a/src/abstractrunner.cpp +++ b/src/abstractrunner.cpp @@ -1,392 +1,392 @@ /* * Copyright 2006-2007 Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractrunner.h" #include "abstractrunner_p.h" #include #include #include #include #include #include #include #include "krunner_debug.h" #include #include #include #include #include #include #include "querymatch.h" #include "runnercontext.h" namespace Plasma { AbstractRunner::AbstractRunner(QObject *parent, const QString &path) : QObject(parent), d(new AbstractRunnerPrivate(this)) { d->init(path); } AbstractRunner::AbstractRunner(const KService::Ptr service, QObject *parent) : QObject(parent), d(new AbstractRunnerPrivate(this)) { d->init(service); } AbstractRunner::AbstractRunner(QObject *parent, const QVariantList &args) : QObject(parent), d(new AbstractRunnerPrivate(this)) { if (args.count() > 0) { KService::Ptr service = KService::serviceByStorageId(args[0].toString()); if (service) { d->init(service); } } } AbstractRunner::~AbstractRunner() { delete d; } KConfigGroup AbstractRunner::config() const { QString group = id(); if (group.isEmpty()) { group = QStringLiteral("UnnamedRunner"); } KConfigGroup runners(KSharedConfig::openConfig(), "Runners"); return KConfigGroup(&runners, group); } void AbstractRunner::reloadConfiguration() { } void AbstractRunner::addSyntax(const RunnerSyntax &syntax) { d->syntaxes.append(syntax); } void AbstractRunner::setDefaultSyntax(const RunnerSyntax &syntax) { d->syntaxes.append(syntax); d->defaultSyntax = &(d->syntaxes.last()); } void AbstractRunner::setSyntaxes(const QList &syntaxes) { d->syntaxes = syntaxes; } QList AbstractRunner::syntaxes() const { return d->syntaxes; } RunnerSyntax *AbstractRunner::defaultSyntax() const { return d->defaultSyntax; } void AbstractRunner::performMatch(Plasma::RunnerContext &localContext) { static const int reasonableRunTime = 1500; static const int fastEnoughTime = 250; if (d->suspendMatching) { return; } QTime time; time.restart(); //The local copy is already obtained in the job match(localContext); // automatically rate limit runners that become slooow const int runtime = time.elapsed(); bool slowed = speed() == SlowSpeed; if (!slowed && runtime > reasonableRunTime) { // we punish runners that return too slowly, even if they don't bring // back matches #ifndef NDEBUG // qCDebug(KRUNNER) << id() << "runner is too slow, putting it on the back burner."; #endif d->fastRuns = 0; setSpeed(SlowSpeed); } if (slowed && runtime < fastEnoughTime && localContext.query().size() > 2) { ++d->fastRuns; if (d->fastRuns > 2) { // we reward slowed runners who bring back matches fast enough // 3 times in a row #ifndef NDEBUG // qCDebug(KRUNNER) << id() << "runner is faster than we thought, kicking it up a notch"; #endif setSpeed(NormalSpeed); } } } QList AbstractRunner::actionsForMatch(const Plasma::QueryMatch &match) { Q_UNUSED(match) QList ret; return ret; } QAction* AbstractRunner::addAction(const QString &id, const QIcon &icon, const QString &text) { QAction *a = new QAction(icon, text, this); d->actions.insert(id, a); return a; } void AbstractRunner::addAction(const QString &id, QAction *action) { d->actions.insert(id, action); } void AbstractRunner::removeAction(const QString &id) { QAction *a = d->actions.take(id); delete a; } QAction* AbstractRunner::action(const QString &id) const { return d->actions.value(id); } QHash AbstractRunner::actions() const { return d->actions; } void AbstractRunner::clearActions() { qDeleteAll(d->actions); d->actions.clear(); } QMimeData *AbstractRunner::mimeDataForMatch(const QueryMatch &match) { if (match.urls().isEmpty()) { return nullptr; } QMimeData *result = new QMimeData(); result->setUrls(match.urls()); return result; } bool AbstractRunner::hasRunOptions() { return d->hasRunOptions; } void AbstractRunner::setHasRunOptions(bool hasRunOptions) { d->hasRunOptions = hasRunOptions; } void AbstractRunner::createRunOptions(QWidget *parent) { Q_UNUSED(parent); } AbstractRunner::Speed AbstractRunner::speed() const { // the only time the read lock will fail is if we were slow are going to speed up // or if we were fast and are going to slow down; so don't wait in this case, just // say we're slow. we either will be soon or were just a moment ago and it doesn't // hurt to do one more run the slow way if (!d->speedLock.tryLockForRead()) { return SlowSpeed; } Speed s = d->speed; d->speedLock.unlock(); return s; } void AbstractRunner::setSpeed(Speed speed) { d->speedLock.lockForWrite(); d->speed = speed; d->speedLock.unlock(); } AbstractRunner::Priority AbstractRunner::priority() const { return d->priority; } void AbstractRunner::setPriority(Priority priority) { d->priority = priority; } RunnerContext::Types AbstractRunner::ignoredTypes() const { return d->blackListed; } void AbstractRunner::setIgnoredTypes(RunnerContext::Types types) { d->blackListed = types; } void AbstractRunner::run(const Plasma::RunnerContext &search, const Plasma::QueryMatch &action) { Q_UNUSED(search); Q_UNUSED(action); } QStringList AbstractRunner::categories() const { return QStringList() << name(); } QIcon AbstractRunner::categoryIcon(const QString&) const { return icon(); } void AbstractRunner::match(Plasma::RunnerContext &) { } QString AbstractRunner::name() const { if (d->runnerDescription.isValid()) { return d->runnerDescription.name(); } return objectName(); } QIcon AbstractRunner::icon() const { if (d->runnerDescription.isValid()) { return QIcon::fromTheme(d->runnerDescription.icon()); } return QIcon(); } QString AbstractRunner::id() const { if (d->runnerDescription.isValid()) { return d->runnerDescription.pluginName(); } return objectName(); } QString AbstractRunner::description() const { if (d->runnerDescription.isValid()) { return d->runnerDescription.property(QStringLiteral("Comment")).toString(); } return objectName(); } KPluginInfo AbstractRunner::metadata() const { return d->runnerDescription; } Package AbstractRunner::package() const { return d->package ? *d->package : Package(); } void AbstractRunner::init() { reloadConfiguration(); } DataEngine *AbstractRunner::dataEngine(const QString &name) const { return d->dataEngine(name); } bool AbstractRunner::isMatchingSuspended() const { return d->suspendMatching; } void AbstractRunner::suspendMatching(bool suspend) { if (d->suspendMatching == suspend) { return; } d->suspendMatching = suspend; emit matchingSuspended(suspend); } AbstractRunnerPrivate::AbstractRunnerPrivate(AbstractRunner *r) : priority(AbstractRunner::NormalPriority), speed(AbstractRunner::NormalSpeed), blackListed(RunnerContext::None), runner(r), fastRuns(0), package(nullptr), defaultSyntax(nullptr), hasRunOptions(false), suspendMatching(false) { } AbstractRunnerPrivate::~AbstractRunnerPrivate() { delete package; package = nullptr; } void AbstractRunnerPrivate::init(const KService::Ptr service) { runnerDescription = KPluginInfo(service); } void AbstractRunnerPrivate::init(const QString &path) { - runnerDescription = KPluginInfo(path + "/metadata.desktop"); + runnerDescription = KPluginInfo(path + QStringLiteral("/metadata.desktop")); const QString api = runnerDescription.property(QStringLiteral("X-Plasma-API")).toString(); } } // Plasma namespace #include "moc_abstractrunner.cpp" diff --git a/src/declarative/runnermodelplugin.cpp b/src/declarative/runnermodelplugin.cpp index c438f80..5796564 100644 --- a/src/declarative/runnermodelplugin.cpp +++ b/src/declarative/runnermodelplugin.cpp @@ -1,39 +1,39 @@ /* * Copyright 2011 by Marco Martin * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runnermodelplugin.h" #include #include "krunner_debug.h" #include #include "runnermodel.h" void RunnerModelPlugin::registerTypes(const char *uri) { qCWarning(KRUNNER) << "Using deprecated import org.kde.runnermodel, please port to org.kde.plasma.core"; - Q_ASSERT(uri == QLatin1String("org.kde.runnermodel")); + Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.runnermodel")); qmlRegisterType(uri, 2, 0, "RunnerModel"); qmlRegisterInterface("QueryMatch"); qRegisterMetaType("QueryMatch"); } diff --git a/src/querymatch.cpp b/src/querymatch.cpp index 210ca27..9c61915 100644 --- a/src/querymatch.cpp +++ b/src/querymatch.cpp @@ -1,351 +1,351 @@ /* * Copyright 2006-2007 Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "querymatch.h" #include #include #include #include #include #include #include #include "krunner_debug.h" #include "abstractrunner.h" namespace Plasma { class QueryMatchPrivate : public QSharedData { public: QueryMatchPrivate(AbstractRunner *r) : QSharedData(), lock(new QReadWriteLock(QReadWriteLock::Recursive)), runner(r), type(QueryMatch::ExactMatch), relevance(.7), selAction(nullptr), enabled(true), idSetByData(false) { } QueryMatchPrivate(const QueryMatchPrivate &other) : QSharedData(other), lock(new QReadWriteLock(QReadWriteLock::Recursive)) { QReadLocker l(other.lock); runner = other.runner; type = other.type; relevance = other.relevance; selAction = other.selAction; enabled = other.enabled; idSetByData = other.idSetByData; matchCategory = other.matchCategory; id = other.id; text = other.text; subtext = other.subtext; icon = other.icon; iconName = other.iconName; data = other.data; mimeType = other.mimeType; urls = other.urls; } ~QueryMatchPrivate() { delete lock; } QReadWriteLock *lock; QPointer runner; QueryMatch::Type type; QString matchCategory; QString id; QString text; QString subtext; QString mimeType; QList urls; QIcon icon; QString iconName; QVariant data; qreal relevance; QAction *selAction; bool enabled : 1; bool idSetByData : 1; }; QueryMatch::QueryMatch(AbstractRunner *runner) : d(new QueryMatchPrivate(runner)) { // qCDebug(KRUNNER) << "new match created"; } QueryMatch::QueryMatch(const QueryMatch &other) : d(other.d) { } QueryMatch::~QueryMatch() { } bool QueryMatch::isValid() const { return d->runner != nullptr; } QString QueryMatch::id() const { if (d->id.isEmpty() && d->runner) { return d->runner.data()->id(); } return d->id; } void QueryMatch::setType(Type type) { d->type = type; } QueryMatch::Type QueryMatch::type() const { return d->type; } void QueryMatch::setMatchCategory(const QString &category) { d->matchCategory = category; } QString QueryMatch::matchCategory() const { if (d->matchCategory.isEmpty() && d->runner) { return d->runner->name(); } return d->matchCategory; } void QueryMatch::setRelevance(qreal relevance) { d->relevance = qMax(qreal(0.0), relevance); } qreal QueryMatch::relevance() const { return d->relevance; } AbstractRunner* QueryMatch::runner() const { return d->runner.data(); } void QueryMatch::setText(const QString &text) { QWriteLocker locker(d->lock); d->text = text; } void QueryMatch::setSubtext(const QString &subtext) { QWriteLocker locker(d->lock); d->subtext = subtext; } void QueryMatch::setData(const QVariant & data) { QWriteLocker locker(d->lock); d->data = data; if (d->id.isEmpty() || d->idSetByData) { const QString id = data.toString(); if (!id.isEmpty()) { setId(data.toString()); d->idSetByData = true; } } } void QueryMatch::setId(const QString &id) { QWriteLocker locker(d->lock); if (d->runner) { d->id = d->runner.data()->id(); } if (!id.isEmpty()) { - d->id.append('_').append(id); + d->id.append(QLatin1Char('_')).append(id); } d->idSetByData = false; } void QueryMatch::setIcon(const QIcon &icon) { QWriteLocker locker(d->lock); d->icon = icon; } void QueryMatch::setIconName(const QString &iconName) { QWriteLocker locker(d->lock); d->iconName = iconName; } QVariant QueryMatch::data() const { QReadLocker locker(d->lock); return d->data; } QString QueryMatch::text() const { QReadLocker locker(d->lock); return d->text; } QString QueryMatch::subtext() const { QReadLocker locker(d->lock); return d->subtext; } QIcon QueryMatch::icon() const { QReadLocker locker(d->lock); return d->icon; } QString QueryMatch::iconName() const { QReadLocker locker(d->lock); return d->iconName; } void QueryMatch::setMimeType(const QString &mimeType) { QWriteLocker locker(d->lock); d->mimeType = mimeType; } QString QueryMatch::mimeType() const { QReadLocker locker(d->lock); return d->mimeType; } void QueryMatch::setUrls(const QList &urls) { QWriteLocker locker(d->lock); d->urls = urls; } QList QueryMatch::urls() const { QReadLocker locker(d->lock); return d->urls; } void QueryMatch::setEnabled(bool enabled) { d->enabled = enabled; } bool QueryMatch::isEnabled() const { return d->enabled && d->runner; } QAction* QueryMatch::selectedAction() const { return d->selAction; } void QueryMatch::setSelectedAction(QAction *action) { d->selAction = action; } bool QueryMatch::operator<(const QueryMatch &other) const { if (d->type == other.d->type) { if (isEnabled() != other.isEnabled()) { return other.isEnabled(); } if (d->relevance != other.d->relevance) { return d->relevance < other.d->relevance; } QReadLocker locker(d->lock); QReadLocker otherLocker(other.d->lock); // when resorting to sort by alpha, we want the // reverse sort order! return d->text > other.d->text; } return d->type < other.d->type; } QueryMatch &QueryMatch::operator=(const QueryMatch &other) { if (d != other.d) { d = other.d; } return *this; } bool QueryMatch::operator==(const QueryMatch &other) const { return (d == other.d); } bool QueryMatch::operator!=(const QueryMatch &other) const { return (d != other.d); } void QueryMatch::run(const RunnerContext &context) const { //qCDebug(KRUNNER) << "we run the term" << context->query() << "whose type is" << context->mimetype(); if (d->runner) { d->runner.data()->run(context, *this); } } bool QueryMatch::hasConfigurationInterface() const { return d->runner && d->runner.data()->hasRunOptions(); } void QueryMatch::createConfigurationInterface(QWidget *parent) { if (hasConfigurationInterface()) { d->runner.data()->createRunOptions(parent); } } } // Plasma namespace diff --git a/src/runnercontext.cpp b/src/runnercontext.cpp index b664655..899eb66 100644 --- a/src/runnercontext.cpp +++ b/src/runnercontext.cpp @@ -1,602 +1,602 @@ /* * Copyright 2006-2007 Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runnercontext.h" #include #include #include #include #include #include #include #include "krunner_debug.h" #include #include #include #include #include #include "abstractrunner.h" #include "querymatch.h" //#define LOCK_FOR_READ(d) if (d->policy == Shared) { d->lock.lockForRead(); } //#define LOCK_FOR_WRITE(d) if (d->policy == Shared) { d->lock.lockForWrite(); } //#define UNLOCK(d) if (d->policy == Shared) { d->lock.unlock(); } #define LOCK_FOR_READ(d) d->lock.lockForRead(); #define LOCK_FOR_WRITE(d) d->lock.lockForWrite(); #define UNLOCK(d) d->lock.unlock(); namespace Plasma { /* Corrects the case of the last component in a path (e.g. /usr/liB -> /usr/lib) path: The path to be processed. correctCasePath: The corrected-case path mustBeDir: Tells whether the last component is a folder or doesn't matter Returns true on success and false on error, in case of error, correctCasePath is not modified */ bool correctLastComponentCase(const QString &path, QString &correctCasePath, const bool mustBeDir) { //qCDebug(KRUNNER) << "Correcting " << path; // If the file already exists then no need to search for it. if (QFile::exists(path)) { correctCasePath = path; //qCDebug(KRUNNER) << "Correct path is" << correctCasePath; return true; } const QFileInfo pathInfo(path); const QDir fileDir = pathInfo.dir(); //qCDebug(KRUNNER) << "Directory is" << fileDir; const QString filename = pathInfo.fileName(); //qCDebug(KRUNNER) << "Filename is" << filename; //qCDebug(KRUNNER) << "searching for a" << (mustBeDir ? "directory" : "directory/file"); const QStringList matchingFilenames = fileDir.entryList(QStringList(filename), mustBeDir ? QDir::Dirs : QDir::NoFilter); if (matchingFilenames.empty()) { //qCDebug(KRUNNER) << "No matches found!!\n"; return false; } else { /*if (matchingFilenames.size() > 1) { #ifndef NDEBUG // qCDebug(KRUNNER) << "Found multiple matches!!\n"; #endif }*/ if (fileDir.path().endsWith(QDir::separator())) { correctCasePath = fileDir.path() + matchingFilenames[0]; } else { correctCasePath = fileDir.path() + QDir::separator() + matchingFilenames[0]; } //qCDebug(KRUNNER) << "Correct path is" << correctCasePath; return true; } } /* Corrects the case of a path (e.g. /uSr/loCAL/bIN -> /usr/local/bin) path: The path to be processed. corrected: The corrected-case path Returns true on success and false on error, in case of error, corrected is not modified */ bool correctPathCase(const QString& path, QString &corrected) { // early exit check if (QFile::exists(path)) { corrected = path; return true; } // path components QStringList components = QString(path).split(QDir::separator()); if (components.size() < 1) { return false; } const bool mustBeDir = components.back().isEmpty(); //qCDebug(KRUNNER) << "Components are" << components; if (mustBeDir) { components.pop_back(); } if (components.isEmpty()) { return true; } QString correctPath; const unsigned initialComponents = components.size(); for (unsigned i = 0; i < initialComponents - 1; i ++) { const QString tmp = components[0] + QDir::separator() + components[1]; if (!correctLastComponentCase(tmp, correctPath, components.size() > 2 || mustBeDir)) { //qCDebug(KRUNNER) << "search was not successful"; return false; } components.removeFirst(); components[0] = correctPath; } corrected = correctPath; return true; } class RunnerContextPrivate : public QSharedData { public: RunnerContextPrivate(RunnerContext *context) : QSharedData(), type(RunnerContext::UnknownType), q(context), singleRunnerQueryMode(false) { } RunnerContextPrivate(const RunnerContextPrivate &p) : QSharedData(), launchCounts(p.launchCounts), type(RunnerContext::None), q(p.q), singleRunnerQueryMode(false) { //qCDebug(KRUNNER) << "¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿boo yeah" << type; } ~RunnerContextPrivate() { } /** * Determines type of query && */ void determineType() { // NOTE! this method must NEVER be called from // code that may be running in multiple threads // with the same data. type = RunnerContext::UnknownType; QString path = QDir::cleanPath(KShell::tildeExpand(term)); - int space = path.indexOf(' '); + int space = path.indexOf(QLatin1Char(' ')); if (!QStandardPaths::findExecutable(path.left(space)).isEmpty()) { // it's a shell command if there's a space because that implies // that it has arguments! type = (space > 0) ? RunnerContext::ShellCommand : RunnerContext::Executable; } else { QUrl url = QUrl::fromUserInput(term); // QUrl::fromUserInput assigns http to everything if it cannot match it to // anything else. We do not want that. if (url.scheme() == QStringLiteral("http")) { if (!term.startsWith(QStringLiteral("http"))) { url.setScheme(QString()); } } const bool hasProtocol = !url.scheme().isEmpty(); const bool isLocalProtocol = hasProtocol && KProtocolInfo::protocolClass(url.scheme()) == QLatin1String(":local"); if (hasProtocol && ((!isLocalProtocol && !url.host().isEmpty()) || (isLocalProtocol && url.scheme() != QLatin1String("file")))) { // we either have a network protocol with a host, so we can show matches for it // or we have a non-file url that may be local so a host isn't required type = RunnerContext::NetworkLocation; } else if (isLocalProtocol) { // at this point in the game, we assume we have a path, // but if a path doesn't have any slashes // it's too ambiguous to be sure we're in a filesystem context path = QDir::cleanPath(url.toLocalFile()); //qCDebug(KRUNNER)<< "slash check" << path; - if (hasProtocol || ((path.indexOf('/') != -1 || path.indexOf('\\') != -1))) { + if (hasProtocol || ((path.indexOf(QLatin1Char('/')) != -1 || path.indexOf(QLatin1Char('\\')) != -1))) { QString correctCasePath; if (correctPathCase(path, correctCasePath)) { path = correctCasePath; QFileInfo info(path); //qCDebug(KRUNNER)<< "correct cas epath is" << correctCasePath << info.isSymLink() << // info.isDir() << info.isFile(); if (info.isSymLink()) { path = info.canonicalFilePath(); info = QFileInfo(path); } if (info.isDir()) { type = RunnerContext::Directory; mimeType = QStringLiteral("inode/folder"); } else if (info.isFile()) { type = RunnerContext::File; QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(path); if (!mime.isDefault()) { mimeType = mime.name(); } } } } } } //qCDebug(KRUNNER) << "term2type" << term << type; } void invalidate() { q = &s_dummyContext; } QReadWriteLock lock; QList matches; QMap matchesById; QHash launchCounts; QString term; QString mimeType; QStringList enabledCategories; RunnerContext::Type type; RunnerContext * q; static RunnerContext s_dummyContext; bool singleRunnerQueryMode; }; RunnerContext RunnerContextPrivate::s_dummyContext; RunnerContext::RunnerContext(QObject *parent) : QObject(parent), d(new RunnerContextPrivate(this)) { } //copy ctor RunnerContext::RunnerContext(RunnerContext &other, QObject *parent) : QObject(parent) { LOCK_FOR_READ(other.d) d = other.d; UNLOCK(other.d) } RunnerContext::~RunnerContext() { } RunnerContext &RunnerContext::operator=(const RunnerContext &other) { if (this->d == other.d) { return *this; } QExplicitlySharedDataPointer oldD = d; LOCK_FOR_WRITE(d) LOCK_FOR_READ(other.d) d = other.d; UNLOCK(other.d) UNLOCK(oldD) return *this; } void RunnerContext::reset() { LOCK_FOR_WRITE(d); // We will detach if we are a copy of someone. But we will reset // if we are the 'main' context others copied from. Resetting // one RunnerContext makes all the copies obsolete. // We need to mark the q pointer of the detached RunnerContextPrivate // as dirty on detach to avoid receiving results for old queries d->invalidate(); UNLOCK(d); d.detach(); // Now that we detached the d pointer we need to reset its q pointer d->q = this; // we still have to remove all the matches, since if the // ref count was 1 (e.g. only the RunnerContext is using // the dptr) then we won't get a copy made if (!d->matches.isEmpty()) { d->matchesById.clear(); d->matches.clear(); emit matchesChanged(); } d->term.clear(); d->mimeType.clear(); d->type = UnknownType; d->singleRunnerQueryMode = false; //qCDebug(KRUNNER) << "match count" << d->matches.count(); } void RunnerContext::setQuery(const QString &term) { reset(); if (term.isEmpty()) { return; } d->term = term; d->determineType(); } QString RunnerContext::query() const { // the query term should never be set after // a search starts. in fact, reset() ensures this // and setQuery(QString) calls reset() return d->term; } void RunnerContext::setEnabledCategories(const QStringList& categories) { d->enabledCategories = categories; } QStringList RunnerContext::enabledCategories() const { return d->enabledCategories; } RunnerContext::Type RunnerContext::type() const { return d->type; } QString RunnerContext::mimeType() const { return d->mimeType; } bool RunnerContext::isValid() const { // if our qptr is dirty, we aren't useful anymore LOCK_FOR_READ(d) const bool valid = (d->q != &(d->s_dummyContext)); UNLOCK(d) return valid; } bool RunnerContext::addMatches(const QList &matches) { if (matches.isEmpty() || !isValid()) { //Bail out if the query is empty or the qptr is dirty return false; } LOCK_FOR_WRITE(d) foreach (QueryMatch match, matches) { // Give previously launched matches a slight boost in relevance // The boost smoothly saturates to 0.5; if (int count = d->launchCounts.value(match.id())) { match.setRelevance(match.relevance() + 0.5 * (1-exp(-count*0.3))); } d->matches.append(match); #ifndef NDEBUG if (d->matchesById.contains(match.id())) { // qCDebug(KRUNNER) << "Duplicate match id " << match.id() << "from" << match.runner()->name(); } #endif d->matchesById.insert(match.id(), &d->matches.at(d->matches.size() - 1)); } UNLOCK(d); //qCDebug(KRUNNER)<< "add matches"; // A copied searchContext may share the d pointer, // we always want to sent the signal of the object that created // the d pointer emit d->q->matchesChanged(); return true; } bool RunnerContext::addMatch(const QueryMatch &match) { if (!isValid()) { // Bail out if the qptr is dirty return false; } QueryMatch m(match); // match must be non-const to modify relevance LOCK_FOR_WRITE(d) if (int count = d->launchCounts.value(m.id())) { m.setRelevance(m.relevance() + 0.05 * count); } d->matches.append(m); d->matchesById.insert(m.id(), &d->matches.at(d->matches.size() - 1)); UNLOCK(d); //qCDebug(KRUNNER)<< "added match" << match->text(); emit d->q->matchesChanged(); return true; } bool RunnerContext::removeMatches(const QStringList matchIdList) { if (!isValid()) { return false; } QStringList presentMatchIdList; QList presentMatchList; LOCK_FOR_READ(d) foreach(const QString &matchId, matchIdList) { const QueryMatch* match = d->matchesById.value(matchId, nullptr); if (match) { presentMatchList << match; presentMatchIdList << matchId; } } UNLOCK(d) if (presentMatchIdList.isEmpty()) { return false; } LOCK_FOR_WRITE(d) foreach(const QueryMatch *match, presentMatchList) { d->matches.removeAll(*match); } foreach(const QString &matchId, presentMatchIdList) { d->matchesById.remove(matchId); } UNLOCK(d) emit d->q->matchesChanged(); return true; } bool RunnerContext::removeMatch(const QString matchId) { if (!isValid()) { return false; } LOCK_FOR_READ(d) const QueryMatch* match = d->matchesById.value(matchId, nullptr); UNLOCK(d) if (!match) { return false; } LOCK_FOR_WRITE(d) d->matches.removeAll(*match); d->matchesById.remove(matchId); UNLOCK(d) emit d->q->matchesChanged(); return true; } bool RunnerContext::removeMatches(Plasma::AbstractRunner *runner) { if (!isValid()) { return false; } QList presentMatchList; LOCK_FOR_READ(d) foreach(const QueryMatch &match, d->matches) { if (match.runner() == runner) { presentMatchList << match; } } UNLOCK(d) if (presentMatchList.isEmpty()) { return false; } LOCK_FOR_WRITE(d) foreach (const QueryMatch &match, presentMatchList) { d->matchesById.remove(match.id()); d->matches.removeAll(match); } UNLOCK(d) emit d->q->matchesChanged(); return true; } QList RunnerContext::matches() const { LOCK_FOR_READ(d) QList matches = d->matches; UNLOCK(d); return matches; } QueryMatch RunnerContext::match(const QString &id) const { LOCK_FOR_READ(d) const QueryMatch *match = d->matchesById.value(id, nullptr); UNLOCK(d) if (match) { return *match; } return QueryMatch(nullptr); } void RunnerContext::setSingleRunnerQueryMode(bool enabled) { d->singleRunnerQueryMode = enabled; } bool RunnerContext::singleRunnerQueryMode() const { return d->singleRunnerQueryMode; } void RunnerContext::restore(const KConfigGroup &config) { const QStringList cfgList = config.readEntry("LaunchCounts", QStringList()); const QRegExp r(QStringLiteral("(\\d*) (.*)")); foreach (const QString& entry, cfgList) { r.indexIn(entry); int count = r.cap(1).toInt(); QString id = r.cap(2); d->launchCounts[id] = count; } } void RunnerContext::save(KConfigGroup &config) { QStringList countList; typedef QHash::const_iterator Iterator; Iterator end = d->launchCounts.constEnd(); for (Iterator i = d->launchCounts.constBegin(); i != end; ++i) { countList << QStringLiteral("%2 %1").arg(i.key()).arg(i.value()); } config.writeEntry("LaunchCounts", countList); config.sync(); } void RunnerContext::run(const QueryMatch &match) { ++d->launchCounts[match.id()]; match.run(*this); } } // Plasma namespace #include "moc_runnercontext.cpp" diff --git a/src/runnermanager.cpp b/src/runnermanager.cpp index f04aec7..529ac7f 100644 --- a/src/runnermanager.cpp +++ b/src/runnermanager.cpp @@ -1,839 +1,839 @@ /* * Copyright (C) 2006 Aaron Seigo * Copyright (C) 2007, 2009 Ryan P. Bitanga * Copyright (C) 2008 Jordi Polo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runnermanager.h" #include #include #include #include #include "krunner_debug.h" #include #include #include #include #include #include #include "dbusrunner_p.h" #include "runnerjobs_p.h" #include "plasma/pluginloader.h" #include #include "querymatch.h" using ThreadWeaver::Queue; using ThreadWeaver::Job; //#define MEASURE_PREPTIME namespace Plasma { /***************************************************** * RunnerManager::Private class * *****************************************************/ class RunnerManagerPrivate { public: RunnerManagerPrivate(RunnerManager *parent) : q(parent), deferredRun(nullptr), currentSingleRunner(nullptr), prepped(false), allRunnersPrepped(false), singleRunnerPrepped(false), teardownRequested(false), singleMode(false), singleRunnerWasLoaded(false) { matchChangeTimer.setSingleShot(true); delayTimer.setSingleShot(true); QObject::connect(&matchChangeTimer, SIGNAL(timeout()), q, SLOT(matchesChanged())); QObject::connect(&context, SIGNAL(matchesChanged()), q, SLOT(scheduleMatchesChanged())); QObject::connect(&delayTimer, SIGNAL(timeout()), q, SLOT(unblockJobs())); } ~RunnerManagerPrivate() { KConfigGroup config = configGroup(); context.save(config); } void scheduleMatchesChanged() { matchChangeTimer.start(100); } void matchesChanged() { emit q->matchesChanged(context.matches()); } void loadConfiguration() { KConfigGroup config = configGroup(); // Limit the number of instances of a single normal speed runner and all of the slow runners // to half the number of threads DefaultRunnerPolicy::instance().setCap(qMax(2, Queue::instance()->maximumNumberOfThreads() / 2)); enabledCategories = config.readEntry("enabledCategories", QStringList()); context.restore(config); } KConfigGroup configGroup() { return conf.isValid() ? conf : KConfigGroup(KSharedConfig::openConfig(), "PlasmaRunnerManager"); } void clearSingleRunner() { if (singleRunnerWasLoaded) { delete currentSingleRunner; } currentSingleRunner = nullptr; } void loadSingleRunner() { if (!singleMode || singleModeRunnerId.isEmpty()) { clearSingleRunner(); return; } if (currentSingleRunner) { if (currentSingleRunner->id() == singleModeRunnerId) { return; } clearSingleRunner(); } AbstractRunner *loadedRunner = q->runner(singleModeRunnerId); if (loadedRunner) { singleRunnerWasLoaded = false; currentSingleRunner = loadedRunner; return; } KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/Runner"), QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(singleModeRunnerId)); if (!offers.isEmpty()) { const KService::Ptr &service = offers[0]; currentSingleRunner = loadInstalledRunner(service); if (currentSingleRunner) { emit currentSingleRunner->prepare(); singleRunnerWasLoaded = true; } } } void loadRunners() { KConfigGroup config = configGroup(); KPluginInfo::List offers = RunnerManager::listRunnerInfo(); const bool loadAll = config.readEntry("loadAll", false); const QStringList whiteList = config.readEntry("pluginWhiteList", QStringList()); const bool noWhiteList = whiteList.isEmpty(); KConfigGroup pluginConf; if (conf.isValid()) { pluginConf = KConfigGroup(&conf, "Plugins"); } else { pluginConf = KConfigGroup(KSharedConfig::openConfig(), "Plugins"); } advertiseSingleRunnerIds.clear(); QStringList allCategories; QSet deadRunners; QMutableListIterator it(offers); while (it.hasNext()) { KPluginInfo &description = it.next(); qCDebug(KRUNNER) << "Loading runner: " << description.pluginName(); QString tryExec = description.property(QStringLiteral("TryExec")).toString(); if (!tryExec.isEmpty() && QStandardPaths::findExecutable(tryExec).isEmpty()) { // we don't actually have this application! continue; } const QString runnerName = description.pluginName(); description.load(pluginConf); const bool loaded = runners.contains(runnerName); const bool selected = loadAll || (description.isPluginEnabled() && (noWhiteList || whiteList.contains(runnerName))); const bool singleQueryModeEnabled = description.property(QStringLiteral("X-Plasma-AdvertiseSingleRunnerQueryMode")).toBool(); if (singleQueryModeEnabled) { advertiseSingleRunnerIds.insert(runnerName, description.name()); } if (selected) { AbstractRunner *runner = nullptr; if (!loaded) { runner = loadInstalledRunner(description.service()); } else { runner = runners.value(runnerName); } if (runner) { const QStringList categories = runner->categories(); allCategories << categories; bool allCategoriesDisabled = true; Q_FOREACH (const QString &cat, categories) { if (enabledCategories.contains(cat)) { allCategoriesDisabled = false; break; } } if (enabledCategories.isEmpty() || !allCategoriesDisabled) { qCDebug(KRUNNER) << "Loaded:" << runnerName; runners.insert(runnerName, runner); } else { runners.remove(runnerName); deadRunners.insert(runner); qCDebug(KRUNNER) << "Categories not enabled. Removing runner: " << runnerName; } } } else if (loaded) { //Remove runner deadRunners.insert(runners.take(runnerName)); qCDebug(KRUNNER) << "Plugin disabled. Removing runner: " << runnerName; } } if (enabledCategories.isEmpty()) { enabledCategories = allCategories; } if (!deadRunners.isEmpty()) { QSet > deadJobs; auto it = searchJobs.begin(); while (it != searchJobs.end()) { auto &job = (*it); if (deadRunners.contains(job->runner())) { QObject::disconnect(job->decorator(), SIGNAL(done(ThreadWeaver::JobPointer)), q, SLOT(jobDone(ThreadWeaver::JobPointer))); it = searchJobs.erase(it); deadJobs.insert(job); } else { it++; } } it = oldSearchJobs.begin(); while (it != oldSearchJobs.end()) { auto &job = (*it); if (deadRunners.contains(job->runner())) { it = oldSearchJobs.erase(it); deadJobs.insert(job); } else { it++; } } if (deadJobs.isEmpty()) { qDeleteAll(deadRunners); } else { new DelayedJobCleaner(deadJobs, deadRunners); } } if (!singleRunnerWasLoaded) { // in case we deleted it up above clearSingleRunner(); } #ifndef NDEBUG // qCDebug(KRUNNER) << "All runners loaded, total:" << runners.count(); #endif } AbstractRunner *loadInstalledRunner(const KService::Ptr service) { if (!service) { return nullptr; } AbstractRunner *runner = nullptr; const QString api = service->property(QStringLiteral("X-Plasma-API")).toString(); if (api.isEmpty()) { QVariantList args; args << service->storageId(); if (Plasma::isPluginVersionCompatible(KPluginLoader(*service).pluginVersion())) { QString error; runner = service->createInstance(q, args, &error); if (!runner) { #ifndef NDEBUG // qCDebug(KRUNNER) << "Failed to load runner:" << service->name() << ". error reported:" << error; #endif } } } else if (api == QLatin1String("DBus")){ runner = new DBusRunner(service, q); } else { //qCDebug(KRUNNER) << "got a script runner known as" << api; runner = new AbstractRunner(service, q); } if (runner) { #ifndef NDEBUG // qCDebug(KRUNNER) << "================= loading runner:" << service->name() << "================="; #endif QObject::connect(runner, SIGNAL(matchingSuspended(bool)), q, SLOT(runnerMatchingSuspended(bool))); runner->init(); if (prepped) { emit runner->prepare(); } } return runner; } void jobDone(ThreadWeaver::JobPointer job) { auto runJob = job.dynamicCast(); if (!runJob) { return; } if (deferredRun.isEnabled() && runJob->runner() == deferredRun.runner()) { //qCDebug(KRUNNER) << "job actually done, running now **************"; QueryMatch tmpRun = deferredRun; deferredRun = QueryMatch(nullptr); tmpRun.run(context); } searchJobs.remove(runJob); oldSearchJobs.remove(runJob); if (searchJobs.isEmpty() && context.matches().isEmpty()) { // we finished our run, and there are no valid matches, and so no // signal will have been sent out. so we need to emit the signal // ourselves here emit q->matchesChanged(context.matches()); } checkTearDown(); } void checkTearDown() { //qCDebug(KRUNNER) << prepped << teardownRequested << searchJobs.count() << oldSearchJobs.count(); if (!prepped || !teardownRequested) { return; } if (Queue::instance()->isIdle()) { searchJobs.clear(); oldSearchJobs.clear(); } if (searchJobs.isEmpty() && oldSearchJobs.isEmpty()) { if (allRunnersPrepped) { foreach (AbstractRunner *runner, runners) { emit runner->teardown(); } allRunnersPrepped = false; } if (singleRunnerPrepped) { if (currentSingleRunner) { emit currentSingleRunner->teardown(); } singleRunnerPrepped = false; } emit q->queryFinished(); prepped = false; teardownRequested = false; } } void unblockJobs() { if (searchJobs.isEmpty() && Queue::instance()->isIdle()) { oldSearchJobs.clear(); checkTearDown(); return; } Queue::instance()->reschedule(); } void runnerMatchingSuspended(bool suspended) { if (suspended || !prepped || teardownRequested) { return; } AbstractRunner *runner = qobject_cast(q->sender()); if (runner) { startJob(runner); } } void startJob(AbstractRunner *runner) { if ((runner->ignoredTypes() & context.type()) == 0) { QSharedPointer job(new FindMatchesJob(runner, &context, Queue::instance())); QObject::connect(job->decorator(), SIGNAL(done(ThreadWeaver::JobPointer)), q, SLOT(jobDone(ThreadWeaver::JobPointer))); if (runner->speed() == AbstractRunner::SlowSpeed) { job->setDelayTimer(&delayTimer); } Queue::instance()->enqueue(job); searchJobs.insert(job); } } // Delay in ms before slow runners are allowed to run static const int slowRunDelay = 400; RunnerManager *q; QueryMatch deferredRun; RunnerContext context; QTimer matchChangeTimer; QTimer delayTimer; // Timer to control when to run slow runners QHash runners; QHash advertiseSingleRunnerIds; AbstractRunner* currentSingleRunner; QSet > searchJobs; QSet > oldSearchJobs; KConfigGroup conf; QStringList enabledCategories; QString singleModeRunnerId; bool prepped : 1; bool allRunnersPrepped : 1; bool singleRunnerPrepped : 1; bool teardownRequested : 1; bool singleMode : 1; bool singleRunnerWasLoaded : 1; }; /***************************************************** * RunnerManager::Public class * *****************************************************/ RunnerManager::RunnerManager(QObject *parent) : QObject(parent), d(new RunnerManagerPrivate(this)) { d->loadConfiguration(); //ThreadWeaver::setDebugLevel(true, 4); } RunnerManager::RunnerManager(KConfigGroup &c, QObject *parent) : QObject(parent), d(new RunnerManagerPrivate(this)) { // Should this be really needed? Maybe d->loadConfiguration(c) would make // more sense. d->conf = KConfigGroup(&c, "PlasmaRunnerManager"); d->loadConfiguration(); //ThreadWeaver::setDebugLevel(true, 4); } RunnerManager::~RunnerManager() { if (!qApp->closingDown() && (!d->searchJobs.isEmpty() || !d->oldSearchJobs.isEmpty())) { new DelayedJobCleaner(d->searchJobs + d->oldSearchJobs); } delete d; } void RunnerManager::reloadConfiguration() { KSharedConfig::openConfig()->reparseConfiguration(); d->loadConfiguration(); d->loadRunners(); } void RunnerManager::setAllowedRunners(const QStringList &runners) { KConfigGroup config = d->configGroup(); config.writeEntry("pluginWhiteList", runners); if (!d->runners.isEmpty()) { // this has been called with runners already created. so let's do an instant reload d->loadRunners(); } } void RunnerManager::setEnabledCategories(const QStringList& categories) { KConfigGroup config = d->configGroup(); config.writeEntry("enabledCategories", categories); d->enabledCategories = categories; if (!d->runners.isEmpty()) { d->loadRunners(); } } QStringList RunnerManager::allowedRunners() const { KConfigGroup config = d->configGroup(); return config.readEntry("pluginWhiteList", QStringList()); } QStringList RunnerManager::enabledCategories() const { KConfigGroup config = d->configGroup(); QStringList list = config.readEntry("enabledCategories", QStringList()); if (list.isEmpty()) { Q_FOREACH (AbstractRunner* runner, d->runners) { list << runner->categories(); } } return list; } void RunnerManager::loadRunner(const KService::Ptr service) { KPluginInfo description(service); const QString runnerName = description.pluginName(); if (!runnerName.isEmpty() && !d->runners.contains(runnerName)) { AbstractRunner *runner = d->loadInstalledRunner(service); if (runner) { d->runners.insert(runnerName, runner); } } } void RunnerManager::loadRunner(const QString &path) { if (!d->runners.contains(path)) { AbstractRunner *runner = new AbstractRunner(this, path); connect(runner, SIGNAL(matchingSuspended(bool)), this, SLOT(runnerMatchingSuspended(bool))); d->runners.insert(path, runner); } } AbstractRunner* RunnerManager::runner(const QString &name) const { if (d->runners.isEmpty()) { d->loadRunners(); } return d->runners.value(name, nullptr); } AbstractRunner *RunnerManager::singleModeRunner() const { return d->currentSingleRunner; } void RunnerManager::setSingleModeRunnerId(const QString &id) { d->singleModeRunnerId = id; d->loadSingleRunner(); } QString RunnerManager::singleModeRunnerId() const { return d->singleModeRunnerId; } bool RunnerManager::singleMode() const { return d->singleMode; } void RunnerManager::setSingleMode(bool singleMode) { if (d->singleMode == singleMode) { return; } Plasma::AbstractRunner *prevSingleRunner = d->currentSingleRunner; d->singleMode = singleMode; d->loadSingleRunner(); d->singleMode = d->currentSingleRunner; if (prevSingleRunner != d->currentSingleRunner) { if (d->prepped) { matchSessionComplete(); if (d->singleMode) { setupMatchSession(); } } } } QList RunnerManager::runners() const { return d->runners.values(); } QStringList RunnerManager::singleModeAdvertisedRunnerIds() const { return d->advertiseSingleRunnerIds.keys(); } QString RunnerManager::runnerName(const QString &id) const { if (runner(id)) { return runner(id)->name(); } else { return d->advertiseSingleRunnerIds.value(id, QString()); } } RunnerContext* RunnerManager::searchContext() const { return &d->context; } //Reordering is here so data is not reordered till strictly needed QList RunnerManager::matches() const { return d->context.matches(); } void RunnerManager::run(const QString &matchId) { run(d->context.match(matchId)); } void RunnerManager::run(const QueryMatch &match) { if (!match.isEnabled()) { return; } //TODO: this function is not const as it may be used for learning AbstractRunner *runner = match.runner(); for (auto it = d->searchJobs.constBegin(); it != d->searchJobs.constEnd(); ++it) { if ((*it)->runner() == runner && !(*it)->isFinished()) { #ifndef NDEBUG // qCDebug(KRUNNER) << "deferred run"; #endif d->deferredRun = match; return; } } if (d->deferredRun.isValid()) { d->deferredRun = QueryMatch(nullptr); } d->context.run(match); } QList RunnerManager::actionsForMatch(const QueryMatch &match) { AbstractRunner *runner = match.runner(); if (runner) { return runner->actionsForMatch(match); } return QList(); } QMimeData * RunnerManager::mimeDataForMatch(const QString &id) const { return mimeDataForMatch(d->context.match(id)); } QMimeData * RunnerManager::mimeDataForMatch(const QueryMatch &match) const { AbstractRunner *runner = match.runner(); if (runner) { return runner->mimeDataForMatch(match); } return nullptr; } KPluginInfo::List RunnerManager::listRunnerInfo(const QString &parentApp) { QString constraint; if (parentApp.isEmpty()) { - constraint.append("not exist [X-KDE-ParentApp]"); + constraint.append(QStringLiteral("not exist [X-KDE-ParentApp]")); } else { - constraint.append("[X-KDE-ParentApp] == '").append(parentApp).append("'"); + constraint.append(QStringLiteral("[X-KDE-ParentApp] == '")).append(parentApp).append(QStringLiteral("'")); } KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/Runner"), constraint); return KPluginInfo::fromServices(offers); } void RunnerManager::setupMatchSession() { d->teardownRequested = false; if (d->prepped) { return; } d->prepped = true; if (d->singleMode) { if (d->currentSingleRunner) { emit d->currentSingleRunner->prepare(); d->singleRunnerPrepped = true; } } else { foreach (AbstractRunner *runner, d->runners) { #ifdef MEASURE_PREPTIME QTime t; t.start(); #endif emit runner->prepare(); #ifdef MEASURE_PREPTIME #ifndef NDEBUG // qCDebug(KRUNNER) << t.elapsed() << runner->name(); #endif #endif } d->allRunnersPrepped = true; } } void RunnerManager::matchSessionComplete() { if (!d->prepped) { return; } d->teardownRequested = true; d->checkTearDown(); } void RunnerManager::launchQuery(const QString &term) { launchQuery(term, QString()); } void RunnerManager::launchQuery(const QString &untrimmedTerm, const QString &runnerName) { setupMatchSession(); QString term = untrimmedTerm.trimmed(); setSingleModeRunnerId(runnerName); setSingleMode(d->currentSingleRunner); if (!runnerName.isEmpty() && !d->currentSingleRunner) { reset(); return; } if (term.isEmpty()) { if (d->singleMode && d->currentSingleRunner->defaultSyntax()) { term = d->currentSingleRunner->defaultSyntax()->exampleQueries().first().remove(QRegExp(QStringLiteral(":q:"))); } else { reset(); return; } } if (d->context.query() == term) { // we already are searching for this! return; } if (!d->singleMode && d->runners.isEmpty()) { d->loadRunners(); } reset(); // qCDebug(KRUNNER) << "runners searching for" << term << "on" << runnerName; d->context.setQuery(term); d->context.setEnabledCategories(d->enabledCategories); QHash runable; //if the name is not empty we will launch only the specified runner if (d->singleMode) { runable.insert(QString(), d->currentSingleRunner); d->context.setSingleRunnerQueryMode(true); } else { runable = d->runners; } foreach (Plasma::AbstractRunner *r, runable) { if (r->isMatchingSuspended()) { continue; } d->startJob(r); } // Start timer to unblock slow runners d->delayTimer.start(RunnerManagerPrivate::slowRunDelay); } QString RunnerManager::query() const { return d->context.query(); } void RunnerManager::reset() { // If ThreadWeaver is idle, it is safe to clear previous jobs if (Queue::instance()->isIdle()) { d->oldSearchJobs.clear(); } else { for (auto it = d->searchJobs.constBegin(); it != d->searchJobs.constEnd(); ++it) { Queue::instance()->dequeue((*it)); } d->oldSearchJobs += d->searchJobs; } d->searchJobs.clear(); if (d->deferredRun.isEnabled()) { //qCDebug(KRUNNER) << "job actually done, running now **************"; QueryMatch tmpRun = d->deferredRun; d->deferredRun = QueryMatch(nullptr); tmpRun.run(d->context); } d->context.reset(); } } // Plasma namespace #include "moc_runnermanager.cpp" diff --git a/src/runnersyntax.cpp b/src/runnersyntax.cpp index 5dfedad..7d4d9cf 100644 --- a/src/runnersyntax.cpp +++ b/src/runnersyntax.cpp @@ -1,110 +1,110 @@ /* * Copyright 2009 Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runnersyntax.h" #include namespace Plasma { class RunnerSyntaxPrivate { public: RunnerSyntaxPrivate(const QString &s, const QString &d) : description(d) { exampleQueries.append(s); } QStringList exampleQueries; QString description; QString termDescription; }; RunnerSyntax::RunnerSyntax(const QString &exampleQuery, const QString &description) : d(new RunnerSyntaxPrivate(exampleQuery, description)) { } RunnerSyntax::RunnerSyntax(const RunnerSyntax &other) : d(new RunnerSyntaxPrivate(*other.d)) { } RunnerSyntax::~RunnerSyntax() { delete d; } RunnerSyntax &RunnerSyntax::operator=(const RunnerSyntax &rhs) { *d = *rhs.d; return *this; } void RunnerSyntax::addExampleQuery(const QString &exampleQuery) { d->exampleQueries.append(exampleQuery); } QStringList RunnerSyntax::exampleQueries() const { return d->exampleQueries; } QStringList RunnerSyntax::exampleQueriesWithTermDescription() const { QStringList queries; - const QString termDesc('<' + searchTermDescription() + '>'); + const QString termDesc(QLatin1Char('<') + searchTermDescription() + QLatin1Char('>')); foreach (QString query, d->exampleQueries) { queries << query.replace(QStringLiteral(":q:"), termDesc); } return queries; } void RunnerSyntax::setDescription(const QString &description) { d->description = description; } QString RunnerSyntax::description() const { QString description = d->description; - description.replace(QLatin1String(":q:"), '<' + searchTermDescription() + '>'); + description.replace(QLatin1String(":q:"), QLatin1Char('<') + searchTermDescription() + QLatin1Char('>')); return description; } void RunnerSyntax::setSearchTermDescription(const QString &description) { d->termDescription = description; } QString RunnerSyntax::searchTermDescription() const { if (d->termDescription.isEmpty()) { return i18n("search term"); } return d->termDescription; } } // Plasma namespace