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

KIO

kurifilter.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002  *  Copyright (C) 2000 Yves Arrouye <yves@realnames.com>
00003  *  Copyright (C) 2000,2010 Dawit Alemayehu <adawit at kde.org>
00004  *
00005  *  This library is free software; you can redistribute it and/or
00006  *  modify it under the terms of the GNU Library General Public
00007  *  License as published by the Free Software Foundation; either
00008  *  version 2 of the License, or (at your option) any later version.
00009  *
00010  *  This library is distributed in the hope that it will be useful,
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  *  Library General Public License for more details.
00014  *
00015  *  You should have received a copy of the GNU Library General Public License
00016  *  along with this library; see the file COPYING.LIB.  If not, write to
00017  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  *  Boston, MA 02110-1301, USA.
00019  **/
00020 #include <config.h>
00021 
00022 #include "kurifilter.h"
00023 
00024 #include <kdebug.h>
00025 #include <kiconloader.h>
00026 #include <kservicetypetrader.h>
00027 #include <kmimetype.h>
00028 #include <kstandarddirs.h>
00029 
00030 #include <QtGui/QPixmap>
00031 #include <QtCore/QHashIterator>
00032 #include <QtCore/QStringBuilder>
00033 
00034 typedef QList<KUriFilterPlugin *> KUriFilterPluginList;
00035 typedef QMap<QString, KUriFilterSearchProvider*> SearchProviderMap;
00036 
00037 
00038 static QString lookupIconNameFor(const KUrl &url, KUriFilterData::UriTypes type)
00039 {
00040     QString iconName;
00041 
00042     switch ( type )
00043     {
00044         case KUriFilterData::NetProtocol:
00045             iconName = KMimeType::favIconForUrl(url);
00046             if (iconName.isEmpty())
00047                 iconName = KMimeType::iconNameForUrl( url );
00048             else
00049                 iconName = KStandardDirs::locate("cache", iconName + QLatin1String(".png"));
00050             break;
00051         case KUriFilterData::LocalFile:
00052         case KUriFilterData::LocalDir:
00053         {
00054             iconName = KMimeType::iconNameForUrl( url );
00055             break;
00056         }
00057         case KUriFilterData::Executable:
00058         {
00059             QString exeName = url.path();
00060             exeName = exeName.mid( exeName.lastIndexOf( '/' ) + 1 ); // strip path if given
00061             KService::Ptr service = KService::serviceByDesktopName( exeName );
00062             if (service && service->icon() != QLatin1String( "unknown" ))
00063                 iconName = service->icon();
00064             // Try to find an icon with the same name as the binary (useful for non-kde apps)
00065             // Use iconPath rather than loadIcon() as the latter uses QPixmap (not threadsafe)
00066             else if ( !KIconLoader::global()->iconPath( exeName, KIconLoader::NoGroup, true ).isNull() )
00067                 iconName = exeName;
00068             else
00069                 // not found, use default
00070                 iconName = QLatin1String("system-run");
00071             break;
00072         }
00073         case KUriFilterData::Help:
00074         {
00075             iconName = QLatin1String("khelpcenter");
00076             break;
00077         }
00078         case KUriFilterData::Shell:
00079         {
00080             iconName = QLatin1String("konsole");
00081             break;
00082         }
00083         case KUriFilterData::Error:
00084         case KUriFilterData::Blocked:
00085         {
00086             iconName = QLatin1String("error");
00087             break;
00088         }
00089         default:
00090             break;
00091     }
00092 
00093     return iconName;
00094 }
00095 
00096 
00097 class KUriFilterSearchProvider::KUriFilterSearchProviderPrivate
00098 {
00099 public:
00100     KUriFilterSearchProviderPrivate() {}
00101     KUriFilterSearchProviderPrivate(const KUriFilterSearchProviderPrivate& other)
00102       : desktopEntryName(other.desktopEntryName),
00103         iconName(other.iconName),
00104         name(other.name),
00105         keys(other.keys) {}
00106 
00107 
00108     QString desktopEntryName;
00109     QString iconName;
00110     QString name;
00111     QStringList keys;
00112 };
00113 
00114 KUriFilterSearchProvider::KUriFilterSearchProvider()
00115                          :d(new KUriFilterSearchProvider::KUriFilterSearchProviderPrivate)
00116 {
00117 }
00118 
00119 KUriFilterSearchProvider::KUriFilterSearchProvider(const KUriFilterSearchProvider& other)
00120                          :d(new KUriFilterSearchProvider::KUriFilterSearchProviderPrivate(*(other.d)))
00121 {
00122 }
00123 
00124 KUriFilterSearchProvider::~KUriFilterSearchProvider()
00125 {
00126     delete d;
00127 }
00128 
00129 QString KUriFilterSearchProvider::desktopEntryName() const
00130 {
00131     return d->desktopEntryName;
00132 }
00133 
00134 QString KUriFilterSearchProvider::iconName() const
00135 {
00136     return d->iconName;
00137 }
00138 
00139 QString KUriFilterSearchProvider::name() const
00140 {
00141     return d->name;
00142 }
00143 
00144 QStringList KUriFilterSearchProvider::keys() const
00145 {
00146     return d->keys;
00147 }
00148 
00149 QString KUriFilterSearchProvider::defaultKey() const
00150 {
00151     if (d->keys.isEmpty())
00152         return QString();
00153 
00154     return d->keys.first();
00155 }
00156 
00157 KUriFilterSearchProvider& KUriFilterSearchProvider::operator=(const KUriFilterSearchProvider& other)
00158 {
00159     d->desktopEntryName = other.d->desktopEntryName;
00160     d->iconName = other.d->iconName;
00161     d->keys = other.d->keys;
00162     d->name = other.d->name;
00163     return *this;
00164 }
00165 
00166 void KUriFilterSearchProvider::setDesktopEntryName(const QString& desktopEntryName)
00167 {
00168     d->desktopEntryName = desktopEntryName;
00169 }
00170 
00171 void KUriFilterSearchProvider::setIconName(const QString& iconName)
00172 {
00173     d->iconName = iconName;
00174 }
00175 
00176 void KUriFilterSearchProvider::setName(const QString& name)
00177 {
00178     d->name = name;
00179 }
00180 
00181 void KUriFilterSearchProvider::setKeys(const QStringList& keys)
00182 {
00183     d->keys = keys;
00184 }
00185 
00186 class KUriFilterDataPrivate
00187 {
00188 public:
00189     explicit KUriFilterDataPrivate( const KUrl& u, const QString& typedUrl )
00190       : checkForExecs(true),
00191         wasModified(true),
00192         uriType(KUriFilterData::Unknown),
00193         searchFilterOptions(KUriFilterData::SearchFilterOptionNone),
00194         url(u),
00195         typedString(typedUrl)
00196     {
00197     }
00198 
00199     ~KUriFilterDataPrivate()
00200     {
00201         qDeleteAll(searchProviderMap.begin(), searchProviderMap.end());
00202     }
00203 
00204     void setData( const KUrl& u, const QString& typedUrl )
00205     {
00206         checkForExecs = true;
00207         wasModified = true;
00208         uriType = KUriFilterData::Unknown;
00209         searchFilterOptions = KUriFilterData::SearchFilterOptionNone;
00210 
00211         url = u;
00212         typedString = typedUrl;
00213 
00214         errMsg.clear();
00215         iconName.clear();
00216         absPath.clear();
00217         args.clear();
00218         searchTerm.clear();
00219         searchProvider.clear();
00220         searchTermSeparator = QChar();
00221         alternateDefaultSearchProvider.clear();
00222         alternateSearchProviders.clear();
00223         searchProviderMap.clear();
00224         defaultUrlScheme.clear();
00225     }
00226 
00227     KUriFilterDataPrivate( KUriFilterDataPrivate * data )
00228     {
00229         wasModified = data->wasModified;
00230         checkForExecs = data->checkForExecs;
00231         uriType = data->uriType;
00232         searchFilterOptions = data->searchFilterOptions;
00233 
00234         url = data->url;
00235         typedString = data->typedString;
00236 
00237         errMsg = data->errMsg;
00238         iconName = data->iconName;
00239         absPath = data->absPath;
00240         args = data->args;
00241         searchTerm = data->searchTerm;
00242         searchTermSeparator = data->searchTermSeparator;
00243         searchProvider = data->searchProvider;
00244         alternateDefaultSearchProvider = data->alternateDefaultSearchProvider;
00245         alternateSearchProviders = data->alternateSearchProviders;
00246         searchProviderMap = data->searchProviderMap;
00247         defaultUrlScheme = data->defaultUrlScheme;
00248     }
00249 
00250     bool checkForExecs;
00251     bool wasModified;
00252     KUriFilterData::UriTypes uriType;
00253     KUriFilterData::SearchFilterOptions searchFilterOptions;
00254 
00255     KUrl url;
00256     QString typedString;
00257     QString errMsg;
00258     QString iconName;
00259     QString absPath;
00260     QString args;
00261     QString searchTerm;
00262     QString searchProvider;
00263     QString alternateDefaultSearchProvider;
00264     QString defaultUrlScheme;
00265     QChar searchTermSeparator;
00266 
00267     QStringList alternateSearchProviders;
00268     QStringList searchProviderList;
00269     SearchProviderMap searchProviderMap;
00270 };
00271 
00272 KUriFilterData::KUriFilterData()
00273                :d( new KUriFilterDataPrivate( KUrl(), QString() ) )
00274 {
00275 }
00276 
00277 KUriFilterData::KUriFilterData( const KUrl& url )
00278                :d( new KUriFilterDataPrivate( url, url.url() ) )
00279 {
00280 }
00281 
00282 KUriFilterData::KUriFilterData( const QString& url )
00283                :d( new KUriFilterDataPrivate( KUrl(url), url ) )
00284 {
00285 }
00286 
00287 
00288 KUriFilterData::KUriFilterData( const KUriFilterData& other )
00289                :d( new KUriFilterDataPrivate( other.d ) )
00290 {
00291 }
00292 
00293 KUriFilterData::~KUriFilterData()
00294 {
00295     delete d;
00296 }
00297 
00298 KUrl KUriFilterData::uri() const
00299 {
00300     return d->url;
00301 }
00302 
00303 QString KUriFilterData::errorMsg() const
00304 {
00305     return d->errMsg;
00306 }
00307 
00308 KUriFilterData::UriTypes KUriFilterData::uriType() const
00309 {
00310     return d->uriType;
00311 }
00312 
00313 QString KUriFilterData::absolutePath() const
00314 {
00315     return d->absPath;
00316 }
00317 
00318 bool KUriFilterData::hasAbsolutePath() const
00319 {
00320     return !d->absPath.isEmpty();
00321 }
00322 
00323 QString KUriFilterData::argsAndOptions() const
00324 {
00325     return d->args;
00326 }
00327 
00328 bool KUriFilterData::hasArgsAndOptions() const
00329 {
00330     return !d->args.isEmpty();
00331 }
00332 
00333 bool KUriFilterData::checkForExecutables() const
00334 {
00335     return d->checkForExecs;
00336 }
00337 
00338 QString KUriFilterData::typedString() const
00339 {
00340     return d->typedString;
00341 }
00342 
00343 QString KUriFilterData::searchTerm() const
00344 {
00345     return d->searchTerm;
00346 }
00347 
00348 QChar KUriFilterData::searchTermSeparator() const
00349 {
00350     return d->searchTermSeparator;
00351 }
00352 
00353 QString KUriFilterData::searchProvider() const
00354 {
00355     return d->searchProvider;
00356 }
00357 
00358 QStringList KUriFilterData::preferredSearchProviders() const
00359 {
00360     return d->searchProviderList;
00361 }
00362 
00363 KUriFilterSearchProvider KUriFilterData::queryForSearchProvider(const QString& provider) const
00364 {
00365     const KUriFilterSearchProvider* searchProvider = d->searchProviderMap.value(provider);
00366 
00367     if (searchProvider)
00368         return *(searchProvider);
00369 
00370     return KUriFilterSearchProvider();
00371 }
00372 
00373 QString KUriFilterData::queryForPreferredSearchProvider(const QString& provider) const
00374 {
00375     const KUriFilterSearchProvider* searchProvider = d->searchProviderMap.value(provider);
00376     if (searchProvider)
00377         return (searchProvider->defaultKey() % searchTermSeparator() % searchTerm());
00378     return QString();
00379 }
00380 
00381 QStringList KUriFilterData::allQueriesForSearchProvider(const QString& provider) const
00382 {
00383     const KUriFilterSearchProvider* searchProvider = d->searchProviderMap.value(provider);
00384     if (searchProvider)
00385         return searchProvider->keys();
00386     return QStringList();
00387 }
00388 
00389 QString KUriFilterData::iconNameForPreferredSearchProvider(const QString &provider) const
00390 {
00391     const KUriFilterSearchProvider* searchProvider = d->searchProviderMap.value(provider);
00392     if (searchProvider)
00393         return searchProvider->iconName();
00394     return QString();
00395 }
00396 
00397 QStringList KUriFilterData::alternateSearchProviders() const
00398 {
00399     return d->alternateSearchProviders;
00400 }
00401 
00402 QString KUriFilterData::alternateDefaultSearchProvider() const
00403 {
00404     return d->alternateDefaultSearchProvider;
00405 }
00406 
00407 QString KUriFilterData::defaultUrlScheme() const
00408 {
00409     return d->defaultUrlScheme;
00410 }
00411 
00412 KUriFilterData::SearchFilterOptions KUriFilterData::searchFilteringOptions() const
00413 {
00414     return d->searchFilterOptions;
00415 }
00416 
00417 QString KUriFilterData::iconName()
00418 {
00419     if (d->wasModified) {
00420         d->iconName = lookupIconNameFor(d->url, d->uriType);
00421         d->wasModified = false;
00422     }
00423 
00424     return d->iconName;
00425 }
00426 
00427 void KUriFilterData::setData( const KUrl& url )
00428 {
00429     d->setData(url, url.url());
00430 }
00431 
00432 void KUriFilterData::setData( const QString& url )
00433 {
00434     d->setData(KUrl(url), url);
00435 }
00436 
00437 bool KUriFilterData::setAbsolutePath( const QString& absPath )
00438 {
00439     // Since a malformed URL could possibly be a relative
00440     // URL we tag it as a possible local resource...
00441     if( (d->url.protocol().isEmpty() || d->url.isLocalFile()) )
00442     {
00443         d->absPath = absPath;
00444         return true;
00445     }
00446     return false;
00447 }
00448 
00449 void KUriFilterData::setCheckForExecutables( bool check )
00450 {
00451     d->checkForExecs = check;
00452 }
00453 
00454 void KUriFilterData::setAlternateSearchProviders(const QStringList &providers)
00455 {
00456     d->alternateSearchProviders = providers;
00457 }
00458 
00459 void KUriFilterData::setAlternateDefaultSearchProvider(const QString &provider)
00460 {
00461     d->alternateDefaultSearchProvider = provider;
00462 }
00463 
00464 void KUriFilterData::setDefaultUrlScheme(const QString& scheme)
00465 {
00466     d->defaultUrlScheme = scheme;
00467 }
00468 
00469 void KUriFilterData::setSearchFilteringOptions(SearchFilterOptions options)
00470 {
00471     d->searchFilterOptions = options;
00472 }
00473 
00474 KUriFilterData& KUriFilterData::operator=( const KUrl& url )
00475 {
00476     d->setData(url, url.url());
00477     return *this;
00478 }
00479 
00480 KUriFilterData& KUriFilterData::operator=( const QString& url )
00481 {
00482     d->setData(KUrl(url), url);
00483     return *this;
00484 }
00485 
00486 /*************************  KUriFilterPlugin ******************************/
00487 
00488 KUriFilterPlugin::KUriFilterPlugin( const QString & name, QObject *parent )
00489                  :QObject( parent ), d( 0 )
00490 {
00491     setObjectName( name );
00492 }
00493 
00494 KCModule *KUriFilterPlugin::configModule( QWidget*, const char* ) const
00495 {
00496     return 0;
00497 }
00498 
00499 QString KUriFilterPlugin::configName() const
00500 {
00501     return objectName();
00502 }
00503 
00504 void KUriFilterPlugin::setFilteredUri( KUriFilterData& data, const KUrl& uri ) const
00505 {
00506     data.d->url = uri;
00507     data.d->wasModified = true;
00508     kDebug(7022) << "Got filtered to:" << uri;
00509 }
00510 
00511 void KUriFilterPlugin::setErrorMsg ( KUriFilterData& data,
00512                                      const QString& errmsg ) const
00513 {
00514     data.d->errMsg = errmsg;
00515 }
00516 
00517 void KUriFilterPlugin::setUriType ( KUriFilterData& data,
00518                                     KUriFilterData::UriTypes type) const
00519 {
00520     data.d->uriType = type;
00521     data.d->wasModified = true;
00522 }
00523 
00524 void KUriFilterPlugin::setArguments( KUriFilterData& data,
00525                                      const QString& args ) const
00526 {
00527     data.d->args = args;
00528 }
00529 
00530 void KUriFilterPlugin::setSearchProvider( KUriFilterData &data, const QString& provider,
00531                                           const QString &term, const QChar &separator) const
00532 {
00533     data.d->searchProvider = provider;
00534     data.d->searchTerm = term;
00535     data.d->searchTermSeparator = separator;
00536 }
00537 
00538 #ifndef KDE_NO_DEPRECATED
00539 void KUriFilterPlugin::setPreferredSearchProviders(KUriFilterData &data, const ProviderInfoList &providers) const
00540 {
00541     QHashIterator<QString, QPair<QString, QString> > it (providers);
00542     while (it.hasNext())
00543     {
00544         it.next();
00545         KUriFilterSearchProvider* searchProvider = data.d->searchProviderMap[it.key()];
00546         searchProvider->setName(it.key());
00547         searchProvider->setIconName(it.value().second);
00548         QStringList keys;
00549         const QStringList queries = it.value().first.split(QLatin1Char(','));
00550         Q_FOREACH(const QString& query,  queries)
00551             keys << query.left(query.indexOf(data.d->searchTermSeparator));
00552         searchProvider->setKeys(keys);
00553     }
00554 }
00555 #endif
00556 
00557 void KUriFilterPlugin::setSearchProviders(KUriFilterData &data, const QList<KUriFilterSearchProvider*>& providers) const
00558 {
00559     Q_FOREACH(KUriFilterSearchProvider* searchProvider, providers) {
00560         data.d->searchProviderList << searchProvider->name();
00561         data.d->searchProviderMap.insert(searchProvider->name(), searchProvider);
00562     }
00563 }
00564 
00565 QString KUriFilterPlugin::iconNameFor(const KUrl& url, KUriFilterData::UriTypes type) const
00566 {
00567     return lookupIconNameFor(url, type);
00568 }
00569 
00570 
00571 /*******************************  KUriFilter ******************************/
00572 
00573 class KUriFilterPrivate
00574 {
00575 public:
00576     KUriFilterPrivate() {}
00577     ~KUriFilterPrivate()
00578     {
00579         qDeleteAll(plugins);
00580         plugins.clear();
00581     }
00582     QHash<QString, KUriFilterPlugin *> plugins;
00583     // NOTE: DO NOT REMOVE this variable! Read the
00584     // comments in KUriFilter::loadPlugins to understand why...
00585     QStringList pluginNames; 
00586 };
00587 
00588 KUriFilter *KUriFilter::self()
00589 {
00590     K_GLOBAL_STATIC(KUriFilter, m_self)
00591     return m_self;
00592 }
00593 
00594 KUriFilter::KUriFilter()
00595     : d(new KUriFilterPrivate())
00596 {
00597     loadPlugins();
00598 }
00599 
00600 KUriFilter::~KUriFilter()
00601 {
00602     delete d;
00603 }
00604 
00605 bool KUriFilter::filterUri( KUriFilterData& data, const QStringList& filters )
00606 {
00607     bool filtered = false;
00608 
00609     // If no specific filters were requested, iterate through all the plugins.
00610     // Otherwise, only use available filters.
00611     if( filters.isEmpty() ) {        
00612         QStringListIterator it (d->pluginNames);
00613         while (it.hasNext()) {
00614             KUriFilterPlugin* plugin = d->plugins.value(it.next());
00615             if (plugin &&  plugin->filterUri( data ))
00616                 filtered = true;
00617         }
00618     } else {
00619         QStringListIterator it (filters);
00620         while (it.hasNext()) {
00621             KUriFilterPlugin* plugin = d->plugins.value(it.next());
00622             if (plugin &&  plugin->filterUri( data ))
00623                 filtered = true;
00624         }
00625     }
00626 
00627     return filtered;
00628 }
00629 
00630 bool KUriFilter::filterUri( KUrl& uri, const QStringList& filters )
00631 {
00632     KUriFilterData data(uri);
00633     bool filtered = filterUri( data, filters );
00634     if( filtered ) uri = data.uri();
00635     return filtered;
00636 }
00637 
00638 bool KUriFilter::filterUri( QString& uri, const QStringList& filters )
00639 {
00640     KUriFilterData data(uri);
00641     bool filtered = filterUri( data, filters );
00642     if( filtered )  uri = data.uri().url();
00643     return filtered;
00644 }
00645 
00646 KUrl KUriFilter::filteredUri( const KUrl &uri, const QStringList& filters )
00647 {
00648     KUriFilterData data(uri);
00649     filterUri( data, filters );
00650     return data.uri();
00651 }
00652 
00653 QString KUriFilter::filteredUri( const QString &uri, const QStringList& filters )
00654 {
00655     KUriFilterData data(uri);
00656     filterUri( data, filters );
00657     return data.uri().url();
00658 }
00659 
00660 #ifndef KDE_NO_DEPRECATED
00661 bool KUriFilter::filterSearchUri(KUriFilterData &data)
00662 {
00663     return filterSearchUri(data, (NormalTextFilter | WebShortcutFilter));
00664 }
00665 #endif
00666 
00667 bool KUriFilter::filterSearchUri(KUriFilterData &data, SearchFilterTypes types)
00668 {
00669     QStringList filters;
00670 
00671     if (types & WebShortcutFilter)
00672         filters << "kurisearchfilter";
00673     
00674     if (types & NormalTextFilter)
00675         filters << "kuriikwsfilter";
00676 
00677     return filterUri(data, filters);
00678 }
00679 
00680 
00681 QStringList KUriFilter::pluginNames() const
00682 {
00683     return d->pluginNames;
00684 }
00685 
00686 void KUriFilter::loadPlugins()
00687 {
00688     const KService::List offers = KServiceTypeTrader::self()->query( "KUriFilter/Plugin" );
00689 
00690     // NOTE: Plugin priority is determined by the InitialPreference entry in
00691     // the .desktop files, so the trader result is already sorted and should
00692     // not be manually sorted.    
00693     Q_FOREACH (const KService::Ptr &ptr, offers) {
00694         KUriFilterPlugin *plugin = ptr->createInstance<KUriFilterPlugin>();
00695         if (plugin) {
00696             const QString& pluginName = plugin->objectName();
00697             Q_ASSERT( !pluginName.isEmpty() );
00698             d->plugins.insert(pluginName, plugin );
00699             // Needed to ensure the order of filtering is honored since
00700             // items are ordered arbitarily in a QHash and QMap always
00701             // sorts by keys. Both undesired behavior.
00702             d->pluginNames << pluginName;
00703         }
00704     }
00705 }
00706 
00707 #include "kurifilter.moc"

KIO

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal