• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdelibs API Reference
  • KDE Home
  • Contact Us
 

Plasma

  • sources
  • kde-4.12
  • kdelibs
  • plasma
runnercontext.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2006-2007 Aaron Seigo <aseigo@kde.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Library General Public License as
6  * published by the Free Software Foundation; either version 2, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include "runnercontext.h"
21 
22 #include <cmath>
23 
24 #include <QReadWriteLock>
25 
26 #include <QDir>
27 #include <QFile>
28 #include <QFileInfo>
29 #include <QSharedData>
30 
31 #include <kcompletion.h>
32 #include <kconfiggroup.h>
33 #include <kdebug.h>
34 #include <kmimetype.h>
35 #include <kshell.h>
36 #include <kstandarddirs.h>
37 #include <kurl.h>
38 #include <kprotocolinfo.h>
39 
40 #include "abstractrunner.h"
41 #include "querymatch.h"
42 
43 //#define LOCK_FOR_READ(d) if (d->policy == Shared) { d->lock.lockForRead(); }
44 //#define LOCK_FOR_WRITE(d) if (d->policy == Shared) { d->lock.lockForWrite(); }
45 //#define UNLOCK(d) if (d->policy == Shared) { d->lock.unlock(); }
46 
47 #define LOCK_FOR_READ(d) d->lock.lockForRead();
48 #define LOCK_FOR_WRITE(d) d->lock.lockForWrite();
49 #define UNLOCK(d) d->lock.unlock();
50 
51 namespace Plasma
52 {
53 
54 /*
55 Corrects the case of the last component in a path (e.g. /usr/liB -> /usr/lib)
56 path: The path to be processed.
57 correctCasePath: The corrected-case path
58 mustBeDir: Tells whether the last component is a folder or doesn't matter
59 Returns true on success and false on error, in case of error, correctCasePath is not modified
60 */
61 bool correctLastComponentCase(const QString &path, QString &correctCasePath, const bool mustBeDir)
62 {
63  //kDebug() << "Correcting " << path;
64 
65  // If the file already exists then no need to search for it.
66  if (QFile::exists(path)) {
67  correctCasePath = path;
68  //kDebug() << "Correct path is" << correctCasePath;
69  return true;
70  }
71 
72  const QFileInfo pathInfo(path);
73 
74  const QDir fileDir = pathInfo.dir();
75  //kDebug() << "Directory is" << fileDir;
76 
77  const QString filename = pathInfo.fileName();
78  //kDebug() << "Filename is" << filename;
79 
80  //kDebug() << "searching for a" << (mustBeDir ? "directory" : "directory/file");
81 
82  const QStringList matchingFilenames = fileDir.entryList(QStringList(filename),
83  mustBeDir ? QDir::Dirs : QDir::NoFilter);
84 
85  if (matchingFilenames.empty()) {
86  //kDebug() << "No matches found!!\n";
87  return false;
88  } else {
89  /*if (matchingFilenames.size() > 1) {
90  kDebug() << "Found multiple matches!!\n";
91  }*/
92 
93  if (fileDir.path().endsWith(QDir::separator())) {
94  correctCasePath = fileDir.path() + matchingFilenames[0];
95  } else {
96  correctCasePath = fileDir.path() + QDir::separator() + matchingFilenames[0];
97  }
98 
99  //kDebug() << "Correct path is" << correctCasePath;
100  return true;
101  }
102 }
103 
104 /*
105 Corrects the case of a path (e.g. /uSr/loCAL/bIN -> /usr/local/bin)
106 path: The path to be processed.
107 corrected: The corrected-case path
108 Returns true on success and false on error, in case of error, corrected is not modified
109 */
110 bool correctPathCase(const QString& path, QString &corrected)
111 {
112  // early exit check
113  if (QFile::exists(path)) {
114  corrected = path;
115  return true;
116  }
117 
118  // path components
119  QStringList components = QString(path).split(QDir::separator());
120 
121  if (components.size() < 1) {
122  return false;
123  }
124 
125  const bool mustBeDir = components.back().isEmpty();
126 
127  //kDebug() << "Components are" << components;
128 
129  if (mustBeDir) {
130  components.pop_back();
131  }
132 
133  if (components.isEmpty()) {
134  return true;
135  }
136 
137  QString correctPath;
138  const unsigned initialComponents = components.size();
139  for (unsigned i = 0; i < initialComponents - 1; i ++) {
140  const QString tmp = components[0] + QDir::separator() + components[1];
141 
142  if (!correctLastComponentCase(tmp, correctPath, components.size() > 2 || mustBeDir)) {
143  //kDebug() << "search was not successful";
144  return false;
145  }
146 
147  components.removeFirst();
148  components[0] = correctPath;
149  }
150 
151  corrected = correctPath;
152  return true;
153 }
154 
155 class RunnerContextPrivate : public QSharedData
156 {
157  public:
158  RunnerContextPrivate(RunnerContext *context)
159  : QSharedData(),
160  type(RunnerContext::UnknownType),
161  q(context),
162  singleRunnerQueryMode(false)
163  {
164  }
165 
166  RunnerContextPrivate(const RunnerContextPrivate &p)
167  : QSharedData(),
168  launchCounts(p.launchCounts),
169  type(RunnerContext::None),
170  q(p.q),
171  singleRunnerQueryMode(false)
172  {
173  //kDebug() << "¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿boo yeah" << type;
174  }
175 
176  ~RunnerContextPrivate()
177  {
178  }
179 
184  void determineType()
185  {
186  // NOTE! this method must NEVER be called from
187  // code that may be running in multiple threads
188  // with the same data.
189  type = RunnerContext::UnknownType;
190  QString path = QDir::cleanPath(KShell::tildeExpand(term));
191 
192  int space = path.indexOf(' ');
193  if (!KStandardDirs::findExe(path.left(space)).isEmpty()) {
194  // it's a shell command if there's a space because that implies
195  // that it has arguments!
196  type = (space > 0) ? RunnerContext::ShellCommand :
197  RunnerContext::Executable;
198  } else {
199  KUrl url(term);
200  // check for a normal URL first
201  //kDebug() << url << KProtocolInfo::protocolClass(url.protocol()) << url.hasHost() <<
202  // url.host() << url.isLocalFile() << path << path.indexOf('/');
203  const bool hasProtocol = !url.protocol().isEmpty();
204  const bool isLocalProtocol = KProtocolInfo::protocolClass(url.protocol()) == ":local";
205  if (hasProtocol &&
206  ((!isLocalProtocol && url.hasHost()) ||
207  (isLocalProtocol && url.protocol() != "file"))) {
208  // we either have a network protocol with a host, so we can show matches for it
209  // or we have a non-file url that may be local so a host isn't required
210  type = RunnerContext::NetworkLocation;
211  } else if (isLocalProtocol) {
212  // at this point in the game, we assume we have a path,
213  // but if a path doesn't have any slashes
214  // it's too ambiguous to be sure we're in a filesystem context
215  path = QDir::cleanPath(url.toLocalFile());
216  //kDebug( )<< "slash check" << path;
217  if (hasProtocol || ((path.indexOf('/') != -1 || path.indexOf('\\') != -1))) {
218  QString correctCasePath;
219  if (correctPathCase(path, correctCasePath)) {
220  path = correctCasePath;
221  QFileInfo info(path);
222  //kDebug( )<< "correct cas epath is" << correctCasePath << info.isSymLink() <<
223  // info.isDir() << info.isFile();
224 
225  if (info.isSymLink()) {
226  path = info.canonicalFilePath();
227  info = QFileInfo(path);
228  }
229  if (info.isDir()) {
230  type = RunnerContext::Directory;
231  mimeType = "inode/folder";
232  } else if (info.isFile()) {
233  type = RunnerContext::File;
234  KMimeType::Ptr mimeTypePtr = KMimeType::findByPath(path);
235  if (mimeTypePtr) {
236  mimeType = mimeTypePtr->name();
237  }
238  }
239  }
240  }
241  }
242  }
243 
244  //kDebug() << "term2type" << term << type;
245  }
246 
247  void invalidate()
248  {
249  q = &s_dummyContext;
250  }
251 
252  QReadWriteLock lock;
253  QList<QueryMatch> matches;
254  QMap<QString, const QueryMatch*> matchesById;
255  QHash<QString, int> launchCounts;
256  QString term;
257  QString mimeType;
258  RunnerContext::Type type;
259  RunnerContext * q;
260  static RunnerContext s_dummyContext;
261  bool singleRunnerQueryMode;
262 };
263 
264 RunnerContext RunnerContextPrivate::s_dummyContext;
265 
266 RunnerContext::RunnerContext(QObject *parent)
267  : QObject(parent),
268  d(new RunnerContextPrivate(this))
269 {
270 }
271 
272 //copy ctor
273 RunnerContext::RunnerContext(RunnerContext &other, QObject *parent)
274  : QObject(parent)
275 {
276  LOCK_FOR_READ(other.d)
277  d = other.d;
278  UNLOCK(other.d)
279 }
280 
281 RunnerContext::~RunnerContext()
282 {
283 }
284 
285 RunnerContext &RunnerContext::operator=(const RunnerContext &other)
286 {
287  if (this->d == other.d) {
288  return *this;
289  }
290 
291  QExplicitlySharedDataPointer<Plasma::RunnerContextPrivate> oldD = d;
292  LOCK_FOR_WRITE(d)
293  LOCK_FOR_READ(other.d)
294  d = other.d;
295  UNLOCK(other.d)
296  UNLOCK(oldD)
297  return *this;
298 }
299 
300 void RunnerContext::reset()
301 {
302  // We will detach if we are a copy of someone. But we will reset
303  // if we are the 'main' context others copied from. Resetting
304  // one RunnerContext makes all the copies obsolete.
305 
306  // We need to mark the q pointer of the detached RunnerContextPrivate
307  // as dirty on detach to avoid receiving results for old queries
308  d->invalidate();
309 
310  d.detach();
311 
312  // Now that we detached the d pointer we need to reset its q pointer
313 
314  d->q = this;
315 
316  // we still have to remove all the matches, since if the
317  // ref count was 1 (e.g. only the RunnerContext is using
318  // the dptr) then we won't get a copy made
319  if (!d->matches.isEmpty()) {
320  d->matchesById.clear();
321  d->matches.clear();
322  emit matchesChanged();
323  }
324 
325  d->term.clear();
326  d->mimeType.clear();
327  d->type = UnknownType;
328  d->singleRunnerQueryMode = false;
329  //kDebug() << "match count" << d->matches.count();
330 }
331 
332 void RunnerContext::setQuery(const QString &term)
333 {
334  reset();
335 
336  if (term.isEmpty()) {
337  return;
338  }
339 
340  d->term = term;
341  d->determineType();
342 }
343 
344 QString RunnerContext::query() const
345 {
346  // the query term should never be set after
347  // a search starts. in fact, reset() ensures this
348  // and setQuery(QString) calls reset()
349  return d->term;
350 }
351 
352 RunnerContext::Type RunnerContext::type() const
353 {
354  return d->type;
355 }
356 
357 QString RunnerContext::mimeType() const
358 {
359  return d->mimeType;
360 }
361 
362 bool RunnerContext::isValid() const
363 {
364  // if our qptr is dirty, we aren't useful anymore
365  return (d->q != &(d->s_dummyContext));
366 }
367 
368 bool RunnerContext::addMatches(const QString &term, const QList<QueryMatch> &matches)
369 {
370  Q_UNUSED(term)
371 
372  if (matches.isEmpty() || !isValid()) {
373  //Bail out if the query is empty or the qptr is dirty
374  return false;
375  }
376 
377  LOCK_FOR_WRITE(d)
378  foreach (QueryMatch match, matches) {
379  // Give previously launched matches a slight boost in relevance
380  // The boost smoothly saturates to 0.5;
381  if (int count = d->launchCounts.value(match.id())) {
382  match.setRelevance(match.relevance() + 0.5 * (1-exp(-count*0.3)));
383  }
384 
385  d->matches.append(match);
386 #ifndef NDEBUG
387  if (d->matchesById.contains(match.id())) {
388  kDebug() << "Duplicate match id " << match.id() << "from" << match.runner()->name();
389  }
390 #endif
391  d->matchesById.insert(match.id(), &d->matches.at(d->matches.size() - 1));
392  }
393  UNLOCK(d);
394  //kDebug()<< "add matches";
395  // A copied searchContext may share the d pointer,
396  // we always want to sent the signal of the object that created
397  // the d pointer
398  emit d->q->matchesChanged();
399 
400  return true;
401 }
402 
403 bool RunnerContext::addMatch(const QString &term, const QueryMatch &match)
404 {
405  Q_UNUSED(term)
406 
407  if (!isValid()) {
408  // Bail out if the qptr is dirty
409  return false;
410  }
411 
412  QueryMatch m(match); // match must be non-const to modify relevance
413 
414  LOCK_FOR_WRITE(d)
415 
416  if (int count = d->launchCounts.value(m.id())) {
417  m.setRelevance(m.relevance() + 0.05 * count);
418  }
419 
420  d->matches.append(m);
421  d->matchesById.insert(m.id(), &d->matches.at(d->matches.size() - 1));
422  UNLOCK(d);
423  //kDebug()<< "added match" << match->text();
424  emit d->q->matchesChanged();
425 
426  return true;
427 }
428 
429 bool RunnerContext::removeMatches(const QStringList matchIdList)
430 {
431  if (!isValid()) {
432  return false;
433  }
434 
435  QStringList presentMatchIdList;
436  QList<const QueryMatch*> presentMatchList;
437 
438  LOCK_FOR_READ(d)
439  foreach(const QString &matchId, matchIdList) {
440  const QueryMatch* match = d->matchesById.value(matchId, 0);
441  if (match) {
442  presentMatchList << match;
443  presentMatchIdList << matchId;
444  }
445  }
446  UNLOCK(d)
447 
448  if (presentMatchIdList.isEmpty()) {
449  return false;
450  }
451 
452  LOCK_FOR_WRITE(d)
453  foreach(const QueryMatch *match, presentMatchList) {
454  d->matches.removeAll(*match);
455  }
456  foreach(const QString &matchId, presentMatchIdList) {
457  d->matchesById.remove(matchId);
458  }
459  UNLOCK(d)
460 
461  emit d->q->matchesChanged();
462 
463  return true;
464 }
465 
466 bool RunnerContext::removeMatch(const QString matchId)
467 {
468  if (!isValid()) {
469  return false;
470  }
471  LOCK_FOR_READ(d)
472  const QueryMatch* match = d->matchesById.value(matchId, 0);
473  UNLOCK(d)
474  if (!match) {
475  return false;
476  }
477  LOCK_FOR_WRITE(d)
478  d->matches.removeAll(*match);
479  d->matchesById.remove(matchId);
480  UNLOCK(d)
481  emit d->q->matchesChanged();
482 
483  return true;
484 }
485 
486 bool RunnerContext::removeMatches(Plasma::AbstractRunner *runner)
487 {
488  if (!isValid()) {
489  return false;
490  }
491 
492  QList<QueryMatch> presentMatchList;
493 
494  LOCK_FOR_READ(d)
495  foreach(const QueryMatch &match, d->matches) {
496  if (match.runner() == runner) {
497  presentMatchList << match;
498  }
499  }
500  UNLOCK(d)
501 
502  if (presentMatchList.isEmpty()) {
503  return false;
504  }
505 
506  LOCK_FOR_WRITE(d)
507  foreach (const QueryMatch &match, presentMatchList) {
508  d->matchesById.remove(match.id());
509  d->matches.removeAll(match);
510  }
511  UNLOCK(d)
512 
513  emit d->q->matchesChanged();
514  return true;
515 }
516 
517 QList<QueryMatch> RunnerContext::matches() const
518 {
519  LOCK_FOR_READ(d)
520  QList<QueryMatch> matches = d->matches;
521  UNLOCK(d);
522  return matches;
523 }
524 
525 QueryMatch RunnerContext::match(const QString &id) const
526 {
527  LOCK_FOR_READ(d)
528  const QueryMatch *match = d->matchesById.value(id, 0);
529  UNLOCK(d)
530 
531  if (match) {
532  return *match;
533  }
534 
535  return QueryMatch(0);
536 }
537 
538 void RunnerContext::setSingleRunnerQueryMode(bool enabled)
539 {
540  d->singleRunnerQueryMode = enabled;
541 }
542 
543 bool RunnerContext::singleRunnerQueryMode() const
544 {
545  return d->singleRunnerQueryMode;
546 }
547 
548 void RunnerContext::restore(const KConfigGroup &config)
549 {
550  const QStringList cfgList = config.readEntry("LaunchCounts", QStringList());
551 
552  const QRegExp r("(\\d*) (.*)");
553  foreach (const QString& entry, cfgList) {
554  r.indexIn(entry);
555  int count = r.cap(1).toInt();
556  QString id = r.cap(2);
557  d->launchCounts[id] = count;
558  }
559 }
560 
561 void RunnerContext::save(KConfigGroup &config)
562 {
563  QStringList countList;
564 
565  typedef QHash<QString, int>::const_iterator Iterator;
566  Iterator end = d->launchCounts.constEnd();
567  for (Iterator i = d->launchCounts.constBegin(); i != end; ++i) {
568  countList << QString("%2 %1").arg(i.key()).arg(i.value());
569  }
570 
571  config.writeEntry("LaunchCounts", countList);
572  config.sync();
573 }
574 
575 void RunnerContext::run(const QueryMatch &match)
576 {
577  ++d->launchCounts[match.id()];
578  match.run(*this);
579 }
580 
581 } // Plasma namespace
582 
583 #include "runnercontext.moc"
Plasma::RunnerContext::matchesChanged
void matchesChanged()
UNLOCK
#define UNLOCK(d)
Definition: runnercontext.cpp:49
abstractrunner.h
Plasma::RunnerContext::Type
Type
Definition: runnercontext.h:51
Plasma::RunnerContext::match
QueryMatch match(const QString &id) const
Retrieves a match by id.
Definition: runnercontext.cpp:525
Plasma::AbstractRunner::name
QString name
Definition: abstractrunner.h:69
Plasma::QueryMatch::relevance
qreal relevance() const
The relevance of this action to the search.
Definition: querymatch.cpp:133
Plasma::RunnerContext::save
void save(KConfigGroup &config)
Definition: runnercontext.cpp:561
Plasma::AbstractRunner
An abstract base class for Plasma Runner plugins.
Definition: abstractrunner.h:63
Plasma::RunnerContext::RunnerContext
RunnerContext(QObject *parent=0)
Definition: runnercontext.cpp:266
Plasma::RunnerContext::setSingleRunnerQueryMode
void setSingleRunnerQueryMode(bool enabled)
Sets single runner query mode.
Definition: runnercontext.cpp:538
QObject
Plasma::QueryMatch::run
void run(const RunnerContext &context) const
Requests this match to activae using the given context.
Definition: querymatch.cpp:273
LOCK_FOR_READ
#define LOCK_FOR_READ(d)
Definition: runnercontext.cpp:47
Plasma::RunnerContext::operator=
RunnerContext & operator=(const RunnerContext &other)
Assignment operator.
Definition: runnercontext.cpp:285
Plasma::RunnerContext::type
Type type() const
The type of item the search term might refer to.
Definition: runnercontext.cpp:352
Plasma::RunnerContext::removeMatches
bool removeMatches(const QStringList matchIdList)
Removes lists of matches from the existing list of matches.
Definition: runnercontext.cpp:429
Plasma::RunnerContext::~RunnerContext
~RunnerContext()
Definition: runnercontext.cpp:281
Plasma::RunnerContext::isValid
bool isValid() const
Definition: runnercontext.cpp:362
Plasma::RunnerContext::addMatch
bool addMatch(const QString &term, const QueryMatch &match)
Appends a match to the existing list of matches.
Definition: runnercontext.cpp:403
Plasma::RunnerContext::File
Definition: runnercontext.h:55
Plasma::RunnerContext::restore
void restore(const KConfigGroup &config)
Sets the launch counts for the associated match ids.
Definition: runnercontext.cpp:548
Plasma::QueryMatch::id
QString id() const
a string that can be used as an ID for this match, even between different queries.
Definition: querymatch.cpp:109
Plasma::RunnerContext
The RunnerContext class provides information related to a search, including the search term...
Definition: runnercontext.h:46
Plasma::RunnerContext::matches
QList< QueryMatch > matches() const
Retrieves all available matches for the current search term.
Definition: runnercontext.cpp:517
Plasma::RunnerContext::Executable
Definition: runnercontext.h:57
LOCK_FOR_WRITE
#define LOCK_FOR_WRITE(d)
Definition: runnercontext.cpp:48
Plasma::RunnerContext::ShellCommand
Definition: runnercontext.h:58
Plasma::RunnerContext::NetworkLocation
Definition: runnercontext.h:56
Plasma::RunnerContext::Directory
Definition: runnercontext.h:54
Plasma::RunnerContext::run
void run(const QueryMatch &match)
Run a match using the information from this context.
Definition: runnercontext.cpp:575
Plasma::RunnerContext::reset
void reset()
Resets the search term for this object.
Definition: runnercontext.cpp:300
Plasma::type
static QScriptValue type(QScriptContext *ctx, QScriptEngine *eng)
Definition: easingcurve.cpp:63
Plasma::RunnerContext::singleRunnerQueryMode
bool singleRunnerQueryMode() const
Definition: runnercontext.cpp:543
Plasma::QueryMatch
A match returned by an AbstractRunner in response to a given RunnerContext.
Definition: querymatch.h:47
Plasma::RunnerContext::setQuery
void setQuery(const QString &term)
Sets the query term for this object and attempts to determine the type of the search.
Definition: runnercontext.cpp:332
Plasma::correctPathCase
bool correctPathCase(const QString &path, QString &corrected)
Definition: runnercontext.cpp:110
Plasma::RunnerContext::addMatches
bool addMatches(const QString &term, const QList< QueryMatch > &matches)
Appends lists of matches to the list of matches.
Definition: runnercontext.cpp:368
Plasma::correctLastComponentCase
bool correctLastComponentCase(const QString &path, QString &correctCasePath, const bool mustBeDir)
Definition: runnercontext.cpp:61
runnercontext.h
Plasma::QueryMatch::setRelevance
void setRelevance(qreal relevance)
Sets the relevance of this action for the search it was created for.
Definition: querymatch.cpp:128
Plasma::QueryMatch::runner
AbstractRunner * runner() const
Definition: querymatch.cpp:138
querymatch.h
Plasma::RunnerContext::query
QString query() const
Definition: runnercontext.cpp:344
Plasma::RunnerContext::mimeType
QString mimeType() const
The mimetype that the search term refers to, if discoverable.
Definition: runnercontext.cpp:357
Plasma::RunnerContext::UnknownType
Definition: runnercontext.h:53
Plasma::RunnerContext::removeMatch
bool removeMatch(const QString matchId)
Removes a match from the existing list of matches.
Definition: runnercontext.cpp:466
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:48:34 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

Plasma

Skip menu "Plasma"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs API Reference

Skip menu "kdelibs API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  • kjsembed
  •   WTF
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Nepomuk-Core
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal