diff --git a/docs/Doxyfile.local b/docs/Doxyfile.local index 843c2f8..efbcc3f 100644 --- a/docs/Doxyfile.local +++ b/docs/Doxyfile.local @@ -1,9 +1,10 @@ ### KApiDox Project-specific Overrides File # define so that deprecated API is not skipped PREDEFINED += \ + "KSERVICE_BUILD_DEPRECATED_SINCE(x, y)=1" \ "PLASMA_ENABLE_DEPRECATED_SINCE(x, y)=1" \ "KRUNNER_ENABLE_DEPRECATED_SINCE(x, y)=1" \ "KRUNNER_BUILD_DEPRECATED_SINCE(x, y)=1" \ "KRUNNER_DEPRECATED_VERSION(x, y, t)=" \ "KRUNNER_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5058113..c1d936b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,142 +1,143 @@ add_subdirectory(declarative) set (KF5Runner_SRCS abstractrunner.cpp dbusrunner.cpp runnerjobs.cpp querymatch.cpp runnercontext.cpp runnermanager.cpp runnersyntax.cpp) ecm_qt_declare_logging_category(KF5Runner_SRCS HEADER krunner_debug.h IDENTIFIER KRUNNER CATEGORY_NAME org.kde.krunner DESCRIPTION "KRunner" EXPORT KRUNNER ) set_property(SOURCE "data/org.kde.krunner1.xml" PROPERTY INCLUDE dbusutils_p.h) add_library(KF5Runner ${KF5Runner_SRCS}) add_library(KF5::Runner ALIAS KF5Runner) ecm_generate_export_header(KF5Runner BASE_NAME KRunner GROUP_BASE_NAME KF VERSION ${KF5_VERSION} DEPRECATED_BASE_VERSION 0 - DEPRECATION_VERSIONS 5.28 5.71 + DEPRECATION_VERSIONS 5.28 5.71 5.72 EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} ) set(KRunner_BUILD_INCLUDE_DIRS ${KRunner_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(KF5Runner INTERFACE "$") target_include_directories(KF5Runner PUBLIC "$") target_link_libraries(KF5Runner PUBLIC Qt5::Core KF5::Plasma # Must be public because abstractrunner.h needs plasma/version.h PRIVATE Qt5::DBus Qt5::Gui Qt5::Widgets KF5::ConfigCore KF5::Service KF5::I18n KF5::ThreadWeaver KF5::CoreAddons #KShell KF5::KIOCore #KProtocolInfo ) set_target_properties(KF5Runner PROPERTIES VERSION ${KRUNNER_VERSION_STRING} SOVERSION 5 EXPORT_NAME "Runner" ) ecm_generate_headers(KRunner_CamelCase_HEADERS HEADER_NAMES AbstractRunner RunnerContext RunnerManager RunnerSyntax QueryMatch PREFIX KRunner REQUIRED_HEADERS KRunner_HEADERS ) # Install files install(TARGETS KF5Runner EXPORT KF5RunnerTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${KRunner_CamelCase_HEADERS} DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KRunner/KRunner COMPONENT Devel) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/krunner_export.h ${KRunner_HEADERS} DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KRunner/krunner COMPONENT Devel) install(FILES data/servicetypes/plasma-runner.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) add_custom_target(copy_servicetypes) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin/data/kservicetypes5) add_custom_command(TARGET copy_servicetypes PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/data/servicetypes/plasma-runner.desktop ${CMAKE_BINARY_DIR}/bin/data/kservicetypes5) add_dependencies(KF5Runner copy_servicetypes) ecm_qt_install_logging_categories( EXPORT KRUNNER FILE krunner.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) if(BUILD_QCH) ecm_add_qch( KF5Runner_QCH NAME KRunner BASE_NAME KF5Runner VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KRunner_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS Qt5Core_QCH INCLUDE_DIRS ${KRunner_BUILD_INCLUDE_DIRS} BLANK_MACROS KRUNNER_EXPORT KRUNNER_DEPRECATED KRUNNER_DEPRECATED_EXPORT "KRUNNER_DEPRECATED_VERSION(x, y, t)" "KRUNNER_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)" PREDEFINED_MACROS + "KSERVICE_BUILD_DEPRECATED_SINCE(x, y)=1" "PLASMA_ENABLE_DEPRECATED_SINCE(x, y)=1" TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KRunner LIB_NAME KF5Runner DEPS "core" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KF5_INCLUDE_INSTALL_DIR}/KRunner) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) install(FILES "data/org.kde.krunner1.xml" DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.krunner1.xml) diff --git a/src/abstractrunner.cpp b/src/abstractrunner.cpp index 62674ce..012967e 100644 --- a/src/abstractrunner.cpp +++ b/src/abstractrunner.cpp @@ -1,404 +1,426 @@ /* * 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 "krunner_debug.h" #include #include #include #include #if KRUNNER_BUILD_DEPRECATED_SINCE(5, 65) #include #endif #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 KPluginInfo &pluginInfo, QObject *parent) + : QObject(parent), + d(new AbstractRunnerPrivate(this)) +{ + d->init(pluginInfo); +} + +#if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) AbstractRunner::AbstractRunner(const KService::Ptr service, QObject *parent) : QObject(parent), d(new AbstractRunnerPrivate(this)) { d->init(service); } +#endif AbstractRunner::AbstractRunner(QObject *parent, const QVariantList &args) : QObject(parent), d(new AbstractRunnerPrivate(this)) { if (!args.isEmpty()) { + KPluginInfo pluginInfo(args); + if (pluginInfo.isValid()) { + d->init(pluginInfo); + } +#if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) KService::Ptr service = KService::serviceByStorageId(args[0].toString()); if (service) { d->init(service); } +#endif } } 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; } QElapsedTimer time; time.start(); //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; } #if KRUNNER_BUILD_DEPRECATED_SINCE(5, 71) bool AbstractRunner::hasRunOptions() { return d->hasRunOptions; } #endif #if KRUNNER_BUILD_DEPRECATED_SINCE(5, 71) void AbstractRunner::setHasRunOptions(bool hasRunOptions) { d->hasRunOptions = hasRunOptions; } #endif #if KRUNNER_BUILD_DEPRECATED_SINCE(5, 71) void AbstractRunner::createRunOptions(QWidget *parent) { Q_UNUSED(parent) } #endif 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; } #if KRUNNER_BUILD_DEPRECATED_SINCE(5, 65) Package AbstractRunner::package() const { QT_WARNING_PUSH QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") return Package(); QT_WARNING_POP } #endif 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), defaultSyntax(nullptr), hasRunOptions(false), suspendMatching(false) { } AbstractRunnerPrivate::~AbstractRunnerPrivate() { } +void AbstractRunnerPrivate::init(const KPluginInfo &pluginInfo) +{ + runnerDescription = pluginInfo; +} + +#if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) void AbstractRunnerPrivate::init(const KService::Ptr service) { QT_WARNING_PUSH QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") runnerDescription = KPluginInfo(service); QT_WARNING_POP } +#endif void AbstractRunnerPrivate::init(const QString &path) { 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/abstractrunner.h b/src/abstractrunner.h index 68f9e45..2530702 100644 --- a/src/abstractrunner.h +++ b/src/abstractrunner.h @@ -1,510 +1,528 @@ /* * 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 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. */ #ifndef PLASMA_ABSTRACTRUNNER_H #define PLASMA_ABSTRACTRUNNER_H #include #include #include #include #include #include #include "krunner_export.h" #include "querymatch.h" #include "runnercontext.h" #include "runnersyntax.h" #include #include // for PLASMA_ENABLE_DEPRECATED_SINCE class QAction; class QMimeData; namespace Plasma { class DataEngine; class Package; class QueryMatch; class AbstractRunnerPrivate; /** * @class AbstractRunner abstractrunner.h * * @short An abstract base class for Plasma Runner plugins. * * Be aware that runners have to be thread-safe. This is due to the fact that * each runner is executed in its own thread for each new term. Thus, a runner * may be executed more than once at the same time. See match() for details. * To let krunner expose a global shortcut for the single runner query mode, the runner * must set the "X-Plasma-AdvertiseSingleRunnerMode" key to true in the .desktop file * and set a default syntax. See setDefaultSyntax() for details. * */ class KRUNNER_EXPORT AbstractRunner : public QObject { Q_OBJECT Q_PROPERTY(bool matchingSuspended READ isMatchingSuspended WRITE suspendMatching NOTIFY matchingSuspended) Q_PROPERTY(QString id READ id) Q_PROPERTY(QString description READ description) Q_PROPERTY(QString name READ name) Q_PROPERTY(QIcon icon READ icon) public: /** Specifies a nominal speed for the runner */ enum Speed { SlowSpeed, NormalSpeed }; /** Specifies a priority for the runner */ enum Priority { LowestPriority = 0, LowPriority, NormalPriority, HighPriority, HighestPriority }; /** An ordered list of runners */ typedef QList List; virtual ~AbstractRunner(); /** * This is the main query method. It should trigger creation of * QueryMatch instances through RunnerContext::addMatch and * RunnerContext::addMatches. It is called internally by performMatch(). * * If the runner can run precisely the requested term (RunnerContext::query()), * it should create an exact match by setting the type to RunnerContext::ExactMatch. * The first runner that creates a QueryMatch will be the * default runner. Other runner's matches will be suggested in the * interface. Non-exact matches should be offered via RunnerContext::PossibleMatch. * * The match will be activated via run() if the user selects it. * * Each runner is executed in its own thread. Whenever the user input changes this * method is called again. Thus, it needs to be thread-safe. Also, all matches need * to be reported once this method returns. Asynchronous runners therefore need * to make use of a local event loop to wait for all matches. * * It is recommended to use local status data in async runners. The simplest way is * to have a separate class doing all the work like so: * * \code * void MyFancyAsyncRunner::match(RunnerContext &context) * { * QEventLoop loop; * MyAsyncWorker worker(context); * connect(&worker, &MyAsyncWorker::finished, &loop, &MyAsyncWorker::quit); * worker.work(); * loop.exec(); * } * \endcode * * Here MyAsyncWorker creates all the matches and calls RunnerContext::addMatch * in some internal slot. It emits the finished() signal once done which will * quit the loop and make the match() method return. The local status is kept * entirely in MyAsyncWorker which makes match() trivially thread-safe. * * If a particular match supports multiple actions, set up the corresponding * actions in the actionsForMatch method. Do not call any of the action methods * within this method! * * Execution of the correct action should be handled in the run method. * @caution This method needs to be thread-safe since KRunner will simply * start a new thread for each new term. * * @warning Returning from this method means to end execution of the runner. * * @sa run(), RunnerContext::addMatch, RunnerContext::addMatches, QueryMatch */ virtual void match(Plasma::RunnerContext &context); /** * Triggers a call to match. This will call match() internally. * * @param context the search context used in executing this match. */ void performMatch(Plasma::RunnerContext &context); #if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 71) /** * If the runner has options that the user can interact with to modify * what happens when run or one of the actions created in match * is called, the runner should return true * @deprecated Since 5.0, this feature has been defunct */ KRUNNER_DEPRECATED_VERSION_BELATED(5, 71, 5, 0, "No longer use, feature removed") bool hasRunOptions(); #endif #if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 71) /** * If hasRunOptions() returns true, this method may be called to get * a widget displaying the options the user can interact with to modify * the behaviour of what happens when a given match is selected. * * @param widget the parent of the options widgets. * @deprecated Since 5.0, this feature has been defunct */ KRUNNER_DEPRECATED_VERSION_BELATED(5, 71, 5, 0, "No longer use, feature removed") virtual void createRunOptions(QWidget *widget); #endif /** * Called whenever an exact or possible match associated with this * runner is triggered. * * @param context The context in which the match is triggered, i.e. for which * the match was created. * @param match The actual match to run/execute. */ virtual void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match); /** * Return a list of categories that this runner provides. By default * this list just contains the runners name. It is used by the runner manager * to disable certain runners if all the categories they provide have * been disabled. * * This list of categories is also used to provide a better way to * configure the runner instead of the typical on / off switch. */ virtual QStringList categories() const; /** * Returns the icon which accurately describes the category \p category. * This is meant to be used in a configuration dialog when showing * all the categories. * * By default this returns the icon of the AbstractRunner */ virtual QIcon categoryIcon(const QString& category) const; /** * The nominal speed of the runner. * @see setSpeed */ Speed speed() const; /** * The priority of the runner. * @see setPriority */ Priority priority() const; /** * Returns the OR'ed value of all the Information types (as defined in RunnerContext::Type) * this runner is not interested in. * @return OR'ed value of black listed types */ RunnerContext::Types ignoredTypes() const; /** * Sets the types this runner will ignore * @param types OR'ed listed of ignored types */ void setIgnoredTypes(RunnerContext::Types types); /** * @return the user visible engine name for the Runner */ QString name() const; /** * @return an id string for the Runner */ QString id() const; /** * @return the description of this Runner */ QString description() const; /** * @return the plugin info for this runner */ KPluginInfo metadata() const; /** * @return the icon for this Runner */ QIcon icon() const; #if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 65) // not 5.28 here, this KRUNNER visibility control only added at 5.65 #if PLASMA_ENABLE_DEPRECATED_SINCE(5, 28) // Plasma::Package is defined with this condition /** * Accessor for the associated Package object if any. * * Note that the returned pointer is only valid for the lifetime of * the runner. * * @return the Package object, which may be invalid * @deprecated since 5.28, use KPackage::Package instead, no accessor in this class **/ KRUNNER_DEPRECATED_VERSION(5, 28, "No longer use, feature removed") Package package() const; #endif #endif /** * Signal runner to reload its configuration. */ virtual void reloadConfiguration(); /** * @return the syntaxes the runner has registered that it accepts and understands * @since 4.3 */ QList syntaxes() const; /** * @return the default syntax for the runner or @c nullptr if no default syntax has been defined * * @since 4.4 */ RunnerSyntax *defaultSyntax() const; /** * @return true if the runner is currently busy with non-interuptable work, signaling that * new threads should not be created for it at this time * @since 4.6 */ bool isMatchingSuspended() const; Q_SIGNALS: /** * This signal is emitted when matching is about to commence, giving runners * an opportunity to prepare themselves, e.g. loading data sets or preparing * IPC or network connections. This method should be fast so as not to cause * slow downs. Things that take longer or which should be loaded once and * remain extant for the lifespan of the AbstractRunner should be done in init(). * @see init() * @since 4.4 */ void prepare(); /** * This signal is emitted when a session of matches is complete, giving runners * the opportunity to tear down anything set up as a result of the prepare() * method. * @since 4.4 */ void teardown(); /** * Emitted when the runner enters or exits match suspension * @see matchingSuspended * @since 4.6 */ void matchingSuspended(bool suspended); protected: friend class RunnerManager; friend class RunnerManagerPrivate; explicit AbstractRunner(QObject *parent = nullptr, const QString &path = QString()); +#if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) explicit AbstractRunner(const KService::Ptr service, QObject *parent = nullptr); +#endif + /// @since 5.72 + explicit AbstractRunner(const KPluginInfo &pluginInfo, QObject *parent = nullptr); AbstractRunner(QObject *parent, const QVariantList &args); /** * Sets whether or not the runner is available for match requests. Useful to * prevent more thread spawning when the thread is in a busy state. */ void suspendMatching(bool suspend); /** * Provides access to the runner's configuration object. */ KConfigGroup config() const; #if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 71) /** * Sets whether or not the runner has options for matches * @deprecated Since 5.0, this feature has been defunct */ KRUNNER_DEPRECATED_VERSION_BELATED(5, 71, 5, 0, "No longer use, feature removed") void setHasRunOptions(bool hasRunOptions); #endif /** * Sets the nominal speed of the runner. Only slow runners need * to call this within their constructor because the default * speed is NormalSpeed. Runners that use D-Bus should call * this within their constructors. */ void setSpeed(Speed newSpeed); /** * Sets the priority of the runner. Lower priority runners are executed * only after higher priority runners. */ void setPriority(Priority newPriority); /** * A given match can have more than action that can be performed on it. * For example, a song match returned by a music player runner can be queued, * added to the playlist, or played. * * Call this method to add actions that can be performed by the runner. * Actions must first be added to the runner's action registry. * Note: execution of correct action is left up to the runner. */ virtual QList actionsForMatch(const Plasma::QueryMatch &match); /** * Creates and then adds an action to the action registry. * AbstractRunner assumes ownership of the created action. * * @param id A unique identifier string * @param icon The icon to display * @param text The text to display * @return the created QAction */ QAction* addAction(const QString &id, const QIcon &icon, const QString &text); /** * Adds an action to the runner's action registry. * * The QAction must be created within the GUI thread; * do not create it within the match method of AbstractRunner. * * @param id A unique identifier string * @param action The QAction to be stored */ void addAction(const QString &id, QAction *action); /** * Removes the action from the action registry. * AbstractRunner deletes the action once removed. * * @param id The id of the action to be removed */ void removeAction(const QString &id); /** * Returns the action associated with the id */ QAction* action(const QString &id) const; /** * Returns all registered actions */ QHash actions() const; /** * Clears the action registry. * The action pool deletes the actions. */ void clearActions(); /** * Adds a registered syntax that this runner understands. This is used to * display to the user what this runner can understand and how it can be * used. * * @param syntax the syntax to register * @since 4.3 */ void addSyntax(const RunnerSyntax &syntax); /** * Set @p syntax as the default syntax for the runner; the default syntax will be * substituted to the empty query in single runner mode. This is also used to * display to the user what this runner can understand and how it can be * used. * The default syntax is automatically added to the list of registered syntaxes, there * is no need to add it using addSyntax. * Note that there can be only one default syntax; if called more than once, the last * call will determine the default syntax. * A default syntax (even trivial) is required to advertise single runner mode * * @param syntax the syntax to register and to set as default * @since 4.4 **/ void setDefaultSyntax(const RunnerSyntax &syntax); /** * Sets the list of syntaxes; passing in an empty list effectively clears * the syntaxes. * * @param the syntaxes to register for this runner * @since 4.3 */ void setSyntaxes(const QList &syns); /** * Loads the given DataEngine * * Tries to load the data engine given by @p name. Each engine is * only loaded once, and that instance is re-used on all subsequent * requests. * * If the data engine was not found, an invalid data engine is returned * (see DataEngine::isValid()). * * Note that you should not delete the returned engine. * * @param name Name of the data engine to load * @return pointer to the data engine if it was loaded, * or an invalid data engine if the requested engine * could not be loaded * * @since 4.4 */ Q_INVOKABLE DataEngine *dataEngine(const QString &name) const; /** * Reimplement this slot to run any initialization routines on first load. * By default, it calls reloadConfiguration(); for scripted Runners this * method also sets up the ScriptEngine. */ virtual void init(); /** * Reimplement this slot if you want your runner * to support serialization and drag and drop * @since 4.5 */ virtual QMimeData *mimeDataForMatch(const Plasma::QueryMatch &match); private: AbstractRunnerPrivate *const d; }; } // Plasma namespace #define K_EXPORT_PLASMA_RUNNER( libname, classname ) \ K_PLUGIN_FACTORY(factory, registerPlugin();) \ K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) +/** + * \relates AbstractRunner + * + * Registers a runner plugin with JSON metadata. + * + * \param classname name of the AbstractRunner derived class. + * \param jsonFile name of the JSON file to be compiled into the plugin as metadata + * + * @since 5.72 + */ +#define K_EXPORT_PLASMA_RUNNER_WITH_JSON(classname, jsonFile) \ + K_PLUGIN_FACTORY_WITH_JSON(classname ## Factory, jsonFile, registerPlugin();) \ + K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) + /** * These plugins are Used by the plugin selector dialog to show * configuration options specific to this runner. These options * must be runner global and not pertain to a specific match. */ #define K_EXPORT_RUNNER_CONFIG( name, classname ) \ K_PLUGIN_FACTORY(ConfigFactory, registerPlugin();) \ K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) #endif diff --git a/src/abstractrunner_p.h b/src/abstractrunner_p.h index db63a4f..4da0425 100644 --- a/src/abstractrunner_p.h +++ b/src/abstractrunner_p.h @@ -1,57 +1,62 @@ /* * Copyright 2006-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. */ #ifndef ABSTRACTRUNNER_P_H #define ABSTRACTRUNNER_P_H #include #include "plasma/dataengineconsumer.h" +class KPluginInfo; + namespace Plasma { class AbstractRunner; class AbstractRunnerPrivate : public DataEngineConsumer { public: AbstractRunnerPrivate(AbstractRunner *r); ~AbstractRunnerPrivate(); + void init(const KPluginInfo &pluginInfo); +#if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) void init(const KService::Ptr service); +#endif void init(const QString &path); AbstractRunner::Priority priority; AbstractRunner::Speed speed; RunnerContext::Types blackListed; KPluginInfo runnerDescription; AbstractRunner *runner; int fastRuns; QReadWriteLock speedLock; QHash actions; QList syntaxes; RunnerSyntax *defaultSyntax; bool hasRunOptions : 1; bool suspendMatching : 1; }; } // namespace Plasma #endif diff --git a/src/dbusrunner.cpp b/src/dbusrunner.cpp index e1c1213..4ba7d70 100644 --- a/src/dbusrunner.cpp +++ b/src/dbusrunner.cpp @@ -1,195 +1,195 @@ /* * Copyright (C) 2017,2018 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 "dbusrunner_p.h" #include #include #include #include #include #include #include #include #include "krunner_debug.h" #include "dbusutils_p.h" #define IFACE_NAME "org.kde.krunner1" -DBusRunner::DBusRunner(const KService::Ptr service, QObject *parent) - : Plasma::AbstractRunner(service, parent) +DBusRunner::DBusRunner(const KPluginInfo &pluginInfo, QObject *parent) + : Plasma::AbstractRunner(pluginInfo, parent) , m_mutex(QMutex::NonRecursive) { qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); - QString requestedServiceName = service->property(QStringLiteral("X-Plasma-DBusRunner-Service")).toString(); - m_path = service->property(QStringLiteral("X-Plasma-DBusRunner-Path")).toString(); + QString requestedServiceName = pluginInfo.property(QStringLiteral("X-Plasma-DBusRunner-Service")).toString(); + m_path = pluginInfo.property(QStringLiteral("X-Plasma-DBusRunner-Path")).toString(); if (requestedServiceName.isEmpty() || m_path.isEmpty()) { - qCWarning(KRUNNER) << "Invalid entry:" << service->name(); + qCWarning(KRUNNER) << "Invalid entry:" << pluginInfo.name(); return; } if (requestedServiceName.endsWith(QLatin1Char('*'))) { requestedServiceName.chop(1); //find existing matching names auto namesReply = QDBusConnection::sessionBus().interface()->registeredServiceNames(); if (namesReply.isValid()) { const auto names = namesReply.value(); for (const QString& serviceName : names) { if (serviceName.startsWith(requestedServiceName)) { m_matchingServices << serviceName; } } } //and watch for changes connect(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, [this, requestedServiceName](const QString &serviceName, const QString &oldOwner, const QString &newOwner) { if (!serviceName.startsWith(requestedServiceName)) { return; } if (!oldOwner.isEmpty() && !newOwner.isEmpty()) { //changed owner, but service still exists. Don't need to adjust anything return; } QMutexLocker lock(&m_mutex); if (!newOwner.isEmpty()) { m_matchingServices.insert(serviceName); } if (!oldOwner.isEmpty()) { m_matchingServices.remove(serviceName); } }); } else { //don't check when not wildcarded, as it could be used with DBus-activation m_matchingServices << requestedServiceName; } - if (service->property(QStringLiteral("X-Plasma-Request-Actions-Once")).toBool()) { + if (pluginInfo.property(QStringLiteral("X-Plasma-Request-Actions-Once")).toBool()) { requestActions(); } else { connect(this, &AbstractRunner::prepare, this, &DBusRunner::requestActions); } } DBusRunner::~DBusRunner() = default; void DBusRunner::requestActions() { clearActions(); m_actions.clear(); //in the multi-services case, register separate actions from each plugin in case they happen to be somehow different //then match together in matchForAction() for (const QString &service: qAsConst(m_matchingServices)) { auto getActionsMethod = QDBusMessage::createMethodCall(service, m_path, QStringLiteral(IFACE_NAME), QStringLiteral("Actions")); QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(getActionsMethod); auto watcher = new QDBusPendingCallWatcher(reply); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, service]() { watcher->deleteLater(); QDBusReply reply = *watcher; if (!reply.isValid()) { return; } const auto actions = reply.value(); for(const RemoteAction &action: actions) { auto a = addAction(action.id, QIcon::fromTheme(action.iconName), action.text); a->setData(action.id); m_actions[service].append(a); } }); } } void DBusRunner::match(Plasma::RunnerContext &context) { QSet services; { QMutexLocker lock(&m_mutex); services = m_matchingServices; } //we scope watchers to make sure the lambda that captures context by reference definitely gets disconnected when this function ends QList> watchers; for (const QString& service : qAsConst(services)) { auto matchMethod = QDBusMessage::createMethodCall(service, m_path, QStringLiteral(IFACE_NAME), QStringLiteral("Match")); matchMethod.setArguments(QList({context.query()})); QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(matchMethod); auto watcher = new QDBusPendingCallWatcher(reply); watchers << QSharedPointer(watcher); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, service, &context, reply]() { if (reply.isError()) { qCDebug(KRUNNER) << "Error calling" << service << " :" << reply.error().name() << reply.error().message(); return; } const auto matches = reply.value(); for(const RemoteMatch &match: matches) { Plasma::QueryMatch m(this); m.setText(match.text); m.setId(match.id); m.setData(service); m.setIconName(match.iconName); m.setType(match.type); m.setRelevance(match.relevance); //split is essential items are as native DBus types, optional extras are in the property map (which is obviously a lot slower to parse) m.setUrls(QUrl::fromStringList(match.properties.value(QStringLiteral("urls")).toStringList())); m.setMatchCategory(match.properties.value(QStringLiteral("category")).toString()); m.setSubtext(match.properties.value(QStringLiteral("subtext")).toString()); context.addMatch(m); }; }, Qt::DirectConnection); // process reply in the watcher's thread (aka the one running ::match not the one owning the runner) } //we're done matching when every service replies for (auto w : qAsConst(watchers)) { w->waitForFinished(); } } QList DBusRunner::actionsForMatch(const Plasma::QueryMatch &match) { Q_UNUSED(match) const QString service = match.data().toString(); return m_actions.value(service); } void DBusRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) { Q_UNUSED(context); QString actionId; QString matchId = match.id().mid(id().length() + 1); //QueryMatch::setId mangles the match ID with runnerID + '_'. This unmangles it QString service = match.data().toString(); if (match.selectedAction()) { actionId = match.selectedAction()->data().toString(); } auto runMethod = QDBusMessage::createMethodCall(service, m_path, QStringLiteral(IFACE_NAME), QStringLiteral("Run")); runMethod.setArguments(QList({matchId, actionId})); QDBusConnection::sessionBus().call(runMethod, QDBus::NoBlock); } diff --git a/src/dbusrunner_p.h b/src/dbusrunner_p.h index 56e7062..a54d9c2 100644 --- a/src/dbusrunner_p.h +++ b/src/dbusrunner_p.h @@ -1,49 +1,49 @@ /* * 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. */ #pragma once #include #include "dbusutils_p.h" class OrgKdeKrunner1Interface; #include #include #include #include class DBusRunner : public Plasma::AbstractRunner { Q_OBJECT public: - explicit DBusRunner(const KService::Ptr service, QObject *parent = nullptr); + explicit DBusRunner(const KPluginInfo &pluginInfo, QObject *parent = nullptr); ~DBusRunner() override; void match(Plasma::RunnerContext &context) override; void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &action) override; QList actionsForMatch(const Plasma::QueryMatch &match) override; private: void requestActions(); void setActions(const RemoteActions &remoteActions); QMutex m_mutex; //needed round any variable also accessed from Match QString m_path; QSet m_matchingServices; QHash > m_actions; }; diff --git a/src/runnermanager.cpp b/src/runnermanager.cpp index 5fc9145..64b793c 100644 --- a/src/runnermanager.cpp +++ b/src/runnermanager.cpp @@ -1,870 +1,926 @@ /* * 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 #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())); // Set up tracking of the last time matchesChanged was signalled lastMatchChangeSignalled.start(); QObject::connect(q, &RunnerManager::matchesChanged, q, [&] { lastMatchChangeSignalled.restart(); }); } ~RunnerManagerPrivate() { KConfigGroup config = configGroup(); context.save(config); } void scheduleMatchesChanged() { if(lastMatchChangeSignalled.hasExpired(250)) { matchChangeTimer.stop(); emit q->matchesChanged(context.matches()); } else { matchChangeTimer.start(250 - lastMatchChangeSignalled.elapsed()); } } 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); + auto filterPluginId = [this](const KPluginMetaData &md) -> bool { + return md.pluginId() == singleModeRunnerId; + }; + + KPluginInfo pluginInfo; + const auto plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/krunner"), filterPluginId); + if (!plugins.isEmpty()) { + pluginInfo = KPluginInfo::fromMetaData(plugins[0]); +#if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) + } else { + const KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/Runner"), QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(singleModeRunnerId)); + if (!offers.isEmpty()) { +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") +QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") + pluginInfo = KPluginInfo(offers[0]); +QT_WARNING_POP + } +#endif + } + if (pluginInfo.isValid()) { + currentSingleRunner = loadInstalledRunner(pluginInfo); 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) { -QT_WARNING_PUSH -QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") -QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") - runner = loadInstalledRunner(description.service()); -QT_WARNING_POP + runner = loadInstalledRunner(description); } else { runner = runners.value(runnerName); runner->reloadConfiguration(); } if (runner) { const QStringList categories = runner->categories(); allCategories << categories; bool allCategoriesDisabled = true; for (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.data(), 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) + AbstractRunner *loadInstalledRunner(const KPluginInfo &pluginInfo) { - if (!service) { + if (!pluginInfo.isValid()) { return nullptr; } AbstractRunner *runner = nullptr; - const QString api = service->property(QStringLiteral("X-Plasma-API")).toString(); + const QString api = pluginInfo.property(QStringLiteral("X-Plasma-API")).toString(); if (api.isEmpty()) { - QVariantList args; - args << service->storageId(); - const quint64 pluginVersion = KPluginLoader(*service).pluginVersion(); + KPluginLoader pluginLoader(pluginInfo.libraryPath()); + const quint64 pluginVersion = pluginLoader.pluginVersion(); if (Plasma::isPluginVersionCompatible(pluginVersion)) { - QString error; - runner = service->createInstance(q, args, &error); - if (!runner) { + KPluginFactory *factory = pluginLoader.factory(); + if (factory) { + const QVariantList args { + pluginInfo.entryPath(), + pluginLoader.metaData().toVariantMap(), + }; + runner = factory->create(q, args); + if (!runner) { #ifndef NDEBUG - // qCDebug(KRUNNER) << "Failed to load runner:" << service->name() << ". error reported:" << error; + // qCDebug(KRUNNER) << "Failed to load runner:" << pluginInfo.name() << ". error reported:" << pluginLoader.errorString(); #endif + } + } else { + qCWarning(KRUNNER).nospace() << "Could not load runner " << pluginInfo.name() << ":" + << pluginLoader.errorString() << " (library path was:" << pluginInfo.libraryPath() << ")"; } } else { const QString runnerVersion = QStringLiteral("%1.%2.%3").arg(pluginVersion >> 16).arg((pluginVersion >> 8) & 0x00ff).arg(pluginVersion & 0x0000ff); - qCWarning(KRUNNER) << "Cannot load runner" << service->name() <<"- versions mismatch: KRunner" - << Plasma::versionString()<< "," << service->name() << runnerVersion; + qCWarning(KRUNNER) << "Cannot load runner" << pluginInfo.name() <<"- versions mismatch: KRunner" + << Plasma::versionString()<< "," << pluginInfo.name() << runnerVersion; } } else if (api == QLatin1String("DBus")){ - runner = new DBusRunner(service, q); + runner = new DBusRunner(pluginInfo, q); } else { //qCDebug(KRUNNER) << "got a script runner known as" << api; - runner = new AbstractRunner(service, q); + runner = new AbstractRunner(pluginInfo, q); } if (runner) { #ifndef NDEBUG - // qCDebug(KRUNNER) << "================= loading runner:" << service->name() << "================="; + // qCDebug(KRUNNER) << "================= loading runner:" << pluginInfo.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()); } teardownRequested = true; 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) { for (AbstractRunner *runner : qAsConst(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.data(), 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 QElapsedTimer lastMatchChangeSignalled; 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()) { list.reserve(d->runners.count()); for (AbstractRunner* runner : qAsConst(d->runners)) { list << runner->categories(); } } return list; } +#if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) void RunnerManager::loadRunner(const KService::Ptr service) { QT_WARNING_PUSH QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") KPluginInfo description(service); QT_WARNING_POP - const QString runnerName = description.pluginName(); + loadRunner(description); +} +#endif + +void RunnerManager::loadRunner(const KPluginInfo &pluginInfo) +{ + const QString runnerName = pluginInfo.pluginName(); if (!runnerName.isEmpty() && !d->runners.contains(runnerName)) { - AbstractRunner *runner = d->loadInstalledRunner(service); + AbstractRunner *runner = d->loadInstalledRunner(pluginInfo); 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(QStringLiteral("not exist [X-KDE-ParentApp]")); - } else { - constraint.append(QStringLiteral("[X-KDE-ParentApp] == '")).append(parentApp).append(QLatin1Char('\'')); + // also covers parentApp.isEmpty() + auto filterParentApp = [&parentApp](const KPluginMetaData &md) -> bool { + return md.value(QStringLiteral("X-KDE-ParentApp")) == parentApp; + }; + + KPluginInfo::List pluginInfos = KPluginInfo::fromMetaData(KPluginLoader::findPlugins(QStringLiteral("kf5/krunner"), filterParentApp)); + QSet knownRunnerIds; + knownRunnerIds.reserve(pluginInfos.size()); + for (const KPluginInfo &pluginInfo : qAsConst(pluginInfos)) { + knownRunnerIds.insert(pluginInfo.pluginName()); } - KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/Runner"), constraint); +#if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) + // also search for old .desktop based KRunner plugins + const QString constraint = parentApp.isEmpty() ? + QStringLiteral("not exist [X-KDE-ParentApp] or [X-KDE-ParentApp] == ''") : + QStringLiteral("[X-KDE-ParentApp] == '") + parentApp + QLatin1Char('\''); + + const KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/Runner"), constraint); QT_WARNING_PUSH QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") - return KPluginInfo::fromServices(offers); + const KPluginInfo::List backwardCompatPluginInfos = KPluginInfo::fromServices(offers); QT_WARNING_POP + + for (const KPluginInfo &pluginInfo : backwardCompatPluginInfos) { + if (!knownRunnerIds.contains(pluginInfo.pluginName())) { + if (!pluginInfo.libraryPath().isEmpty()) { + qCWarning(KRUNNER).nospace() << "KRunner plugin " << pluginInfo.pluginName() << " still uses .desktop files (" + << pluginInfo.entryPath() << "). Please port it to JSON metadata."; + } else { + // TODO: warning for D-Bus service info + } + pluginInfos.append(pluginInfo); + } + } +#endif + + return pluginInfos; } 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 { for (AbstractRunner *runner : qAsConst(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(QLatin1String(":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; } for (Plasma::AbstractRunner *r : qAsConst(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(); emit queryFinished(); } } // Plasma namespace #include "moc_runnermanager.cpp" diff --git a/src/runnermanager.h b/src/runnermanager.h index 9eadb56..fb66856 100644 --- a/src/runnermanager.h +++ b/src/runnermanager.h @@ -1,299 +1,313 @@ /* * Copyright (C) 2006 Aaron Seigo * Copyright (C) 2007 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. */ #ifndef PLASMA_RUNNERMANAGER_H #define PLASMA_RUNNERMANAGER_H #include #include #include #include "krunner_export.h" #include "abstractrunner.h" class QAction; class KConfigGroup; namespace Plasma { class QueryMatch; class AbstractRunner; class RunnerContext; class RunnerManagerPrivate; /** * @class RunnerManager runnermanager.h * * @short The RunnerManager class decides what installed runners are runnable, * and their ratings. It is the main proxy to the runners. */ class KRUNNER_EXPORT RunnerManager : public QObject { Q_OBJECT public: explicit RunnerManager(QObject *parent=nullptr); explicit RunnerManager(KConfigGroup &config, QObject *parent=nullptr); ~RunnerManager(); /** * Finds and returns a loaded runner or NULL * @param pluginName the name of the runner plugin * @return Pointer to the runner */ AbstractRunner *runner(const QString &pluginName) const; /** * @return the currently active "single mode" runner, or null if none * @since 4.4 */ AbstractRunner *singleModeRunner() const; /** * Puts the manager into "single runner" mode using the given * runner; if the runner does not exist or can not be loaded then * the single runner mode will not be started and singleModeRunner() * will return NULL * @param id the id of the runner to use * @since 4.4 */ void setSingleModeRunnerId(const QString &id); /** * @return the id of the runner to use in single mode * @since 4.4 */ QString singleModeRunnerId() const; /** * @return true if the manager is set to run in single runner mode * @since 4.4 */ bool singleMode() const; /** * Sets whether or not the manager is in single mode. * * @param singleMode true if the manager should be in single mode, false otherwise * @since 4.4 */ void setSingleMode(bool singleMode); /** * Returns the translated name of a runner * @param id the id of the runner * * @since 4.4 */ QString runnerName(const QString &id) const; /** * @return the list of all currently loaded runners */ QList runners() const; /** * @return the names of all runners that advertise single query mode * @since 4.4 */ QStringList singleModeAdvertisedRunnerIds() const; /** * Retrieves the current context * @return pointer to the current context */ RunnerContext *searchContext() const; /** * Retrieves all available matches found so far for the previously launched query * @return List of matches */ QList matches() const; /** * Runs a given match * @param match the match to be executed */ void run(const QueryMatch &match); /** * Runs a given match * @param id the id of the match to run */ void run(const QString &id); /** * Retrieves the list of actions, if any, for a match */ QList actionsForMatch(const QueryMatch &match); /** * @return the current query term */ QString query() const; /** * Causes a reload of the current configuration */ void reloadConfiguration(); /** * Sets a whitelist for the plugins that can be loaded * * @param plugins the plugin names of allowed runners * @since 4.4 */ void setAllowedRunners(const QStringList &runners); /** * Sets the list of categories which matches should be * returned for. It also internally tries not to execute the * runners which do not fall in this category. */ void setEnabledCategories(const QStringList &categories); +#if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) /** * Attempts to add the AbstractRunner plugin represented * by the KService passed in. Usually one can simply let * the configuration of plugins handle loading Runner plugins, * but in cases where specific runners should be loaded this * allows for that to take place * * @param service the service to use to load the plugin * @since 4.5 */ void loadRunner(const KService::Ptr service); +#endif + + /** + * Attempts to add the AbstractRunner plugin represented + * by the plugin info passed in. Usually one can simply let + * the configuration of plugins handle loading Runner plugins, + * but in cases where specific runners should be loaded this + * allows for that to take place + * + * @param pluginInfo the service to use to load the plugin + * @since 5.72 + */ + void loadRunner(const KPluginInfo &pluginInfo); /** * Attempts to add the AbstractRunner from a Plasma::Package on disk. * Usually one can simply let the configuration of plugins * handle loading Runner plugins, but in cases where specific * runners should be loaded this allows for that to take place * * @param path the path to a Runner package to load * @since 4.5 */ void loadRunner(const QString &path); /** * @return the list of allowed plugins * @since 4.4 */ QStringList allowedRunners() const; /** * @return the list of enabled categories */ QStringList enabledCategories() const; /** * @return mime data of the specified match * @since 4.5 */ QMimeData * mimeDataForMatch(const QueryMatch &match) const; /** * @return mime data of the specified match * @since 4.5 */ QMimeData * mimeDataForMatch(const QString &matchId) const; /** * Returns a list of all known Runner implementations * * @param parentApp the application to filter applets on. Uses the * X-KDE-ParentApp entry (if any) in the plugin info. * The default value of QString() will result in a * list containing only applets not specifically * registered to an application. * @return list of AbstractRunners * @since 4.6 **/ static KPluginInfo::List listRunnerInfo(const QString &parentApp = QString()); public Q_SLOTS: /** * Call this method when the runners should be prepared for a query session. * Call matchSessionComplete when the query session is finished for the time * being. * @since 4.4 * @see matchSessionComplete */ void setupMatchSession(); /** * Call this method when the query session is finished for the time * being. * @since 4.4 * @see prepareForMatchSession */ void matchSessionComplete(); /** * Launch a query, this will create threads and return immediately. * When the information will be available can be known using the * matchesChanged signal. * * @param term the term we want to find matches for * @param runnerId optional, if only one specific runner is to be used; * providing an id will put the manager into single runner mode */ void launchQuery(const QString &term, const QString &runnerId); /** * Convenience version of above */ void launchQuery(const QString &term); /** * Reset the current data and stops the query */ void reset(); Q_SIGNALS: /** * Emitted each time a new match is added to the list */ void matchesChanged(const QList &matches); /** * Emitted when the launchQuery finish * @since 4.5 */ void queryFinished(); private: Q_PRIVATE_SLOT(d, void scheduleMatchesChanged()) Q_PRIVATE_SLOT(d, void matchesChanged()) Q_PRIVATE_SLOT(d, void jobDone(ThreadWeaver::JobPointer)) Q_PRIVATE_SLOT(d, void unblockJobs()) Q_PRIVATE_SLOT(d, void runnerMatchingSuspended(bool)) RunnerManagerPrivate * const d; friend class RunnerManagerPrivate; }; } #endif diff --git a/templates/runner/CMakeLists.txt b/templates/runner/CMakeLists.txt index 0835712..7d75439 100644 --- a/templates/runner/CMakeLists.txt +++ b/templates/runner/CMakeLists.txt @@ -1,20 +1,20 @@ cmake_minimum_required(VERSION 3.0) project(%{APPNAMELC}) -set(QT_MIN_VERSION "5.9.0") -set(KF5_MIN_VERSION "5.42.0") +set(QT_MIN_VERSION "5.12.0") +set(KF5_MIN_VERSION "5.72.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Widgets) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Runner I18n) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(FeatureSummary) add_subdirectory(src) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/templates/runner/src/%{APPNAMELC}.cpp b/templates/runner/src/%{APPNAMELC}.cpp index 30ce4fd..a6b6c7d 100644 --- a/templates/runner/src/%{APPNAMELC}.cpp +++ b/templates/runner/src/%{APPNAMELC}.cpp @@ -1,44 +1,44 @@ /* SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}> SPDX-License-Identifier: LGPL-2.1-or-later */ #include "%{APPNAMELC}.h" // KF #include %{APPNAME}::%{APPNAME}(QObject *parent, const QVariantList &args) : Plasma::AbstractRunner(parent, args) { setObjectName(QStringLiteral("%{APPNAME}")); } %{APPNAME}::~%{APPNAME}() { } void %{APPNAME}::match(Plasma::RunnerContext &context) { const QString term = context.query(); if (term.length() < 3) { return; } // TODO } void %{APPNAME}::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) { Q_UNUSED(context) Q_UNUSED(match) // TODO } -K_EXPORT_PLASMA_RUNNER(%{APPNAMELC}, %{APPNAME}) +K_EXPORT_PLASMA_RUNNER_WITH_JSON(%{APPNAME}, "plasma-runner-%{APPNAMELC}.json") // needed for the QObject subclass declared as part of K_EXPORT_PLASMA_RUNNER #include "%{APPNAMELC}.moc" diff --git a/templates/runner/src/CMakeLists.txt b/templates/runner/src/CMakeLists.txt index b6fabfd..af51af8 100644 --- a/templates/runner/src/CMakeLists.txt +++ b/templates/runner/src/CMakeLists.txt @@ -1,10 +1,11 @@ add_definitions(-DTRANSLATION_DOMAIN=\"plasma_runner_org.kde.%{APPNAMELC}\") set(%{APPNAMELC}_SRCS %{APPNAMELC}.cpp) -add_library(krunner_%{APPNAMELC} MODULE ${%{APPNAMELC}_SRCS}) -target_link_libraries(krunner_%{APPNAMELC} KF5::Runner KF5::I18n) +add_library(%{APPNAMELC} MODULE ${%{APPNAMELC}_SRCS}) +kcoreaddons_desktop_to_json(%{APPNAMELC} plasma-runner-%{APPNAMELC}.desktop) +target_link_libraries(%{APPNAMELC} KF5::Runner KF5::I18n) -install(TARGETS krunner_%{APPNAMELC} DESTINATION ${KDE_INSTALL_PLUGINDIR}) +install(TARGETS %{APPNAMELC} DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/krunner) install(FILES plasma-runner-%{APPNAMELC}.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) diff --git a/templates/runner/src/plasma-runner-%{APPNAMELC}.desktop b/templates/runner/src/plasma-runner-%{APPNAMELC}.desktop index 6b17124..874a1c2 100644 --- a/templates/runner/src/plasma-runner-%{APPNAMELC}.desktop +++ b/templates/runner/src/plasma-runner-%{APPNAMELC}.desktop @@ -1,14 +1,14 @@ [Desktop Entry] Name=%{APPNAME} Comment=%{APPNAME} runner Icon= X-KDE-ServiceTypes=Plasma/Runner Type=Service -X-KDE-Library=krunner_%{APPNAMELC} +X-KDE-Library=kf5/krunner/%{APPNAMELC} X-KDE-PluginInfo-Author=%{AUTHOR} X-KDE-PluginInfo-Email=%{EMAIL} X-KDE-PluginInfo-Name=%{APPNAMELC} X-KDE-PluginInfo-Version=0.1 X-KDE-PluginInfo-License=LGPL 2.1+ X-KDE-PluginInfo-EnabledByDefault=true