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

KIO

previewjob.cpp

Go to the documentation of this file.
00001 // -*- c++ -*-
00002 // vim: ts=4 sw=4 et
00003 /*  This file is part of the KDE libraries
00004     Copyright (C) 2000 David Faure <faure@kde.org>
00005                   2000 Carsten Pfeiffer <pfeiffer@kde.org>
00006                   2001 Malte Starostik <malte.starostik@t-online.de>
00007 
00008     This library is free software; you can redistribute it and/or
00009     modify it under the terms of the GNU Library General Public
00010     License as published by the Free Software Foundation; either
00011     version 2 of the License, or (at your option) any later version.
00012 
00013     This library is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016     Library General Public License for more details.
00017 
00018     You should have received a copy of the GNU Library General Public License
00019     along with this library; see the file COPYING.LIB.  If not, write to
00020     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021     Boston, MA 02110-1301, USA.
00022 */
00023 
00024 #include "previewjob.h"
00025 
00026 #include <sys/stat.h>
00027 #ifdef __FreeBSD__
00028     #include <machine/param.h>
00029 #endif
00030 #include <sys/types.h>
00031 
00032 #ifdef Q_OS_UNIX
00033 #include <sys/ipc.h>
00034 #include <sys/shm.h>
00035 #endif
00036 
00037 #include <QtCore/QDir>
00038 #include <QtCore/QFile>
00039 #include <QtGui/QImage>
00040 #include <QtCore/QTimer>
00041 #include <QtCore/QRegExp>
00042 
00043 #include <kfileitem.h>
00044 #include <kapplication.h>
00045 #include <kde_file.h>
00046 #include <ktemporaryfile.h>
00047 #include <kservicetypetrader.h>
00048 #include <kcodecs.h>
00049 #include <kglobal.h>
00050 #include <kstandarddirs.h>
00051 #include <kservice.h>
00052 #include <QtCore/QLinkedList>
00053 #include <kconfiggroup.h>
00054 
00055 #include "jobuidelegate.h"
00056 #include "job_p.h"
00057 
00058 namespace KIO { struct PreviewItem; }
00059 using namespace KIO;
00060 
00061 struct KIO::PreviewItem
00062 {
00063     KFileItem item;
00064     KService::Ptr plugin;
00065 };
00066 
00067 class KIO::PreviewJobPrivate: public KIO::JobPrivate
00068 {
00069 public:
00070     enum { STATE_STATORIG, // if the thumbnail exists
00071            STATE_GETORIG, // if we create it
00072            STATE_CREATETHUMB // thumbnail:/ slave
00073     } state;
00074     PreviewJob *q;
00075 
00076     KFileItemList initialItems;
00077     QStringList enabledPlugins;
00078     // Our todo list :)
00079     // We remove the first item at every step, so use QLinkedList
00080     QLinkedList<PreviewItem> items;
00081     // The current item
00082     PreviewItem currentItem;
00083     // The modification time of that URL
00084     time_t tOrig;
00085     // Path to thumbnail cache for the current size
00086     QString thumbPath;
00087     // Original URL of current item in TMS format
00088     // (file:///path/to/file instead of file:/path/to/file)
00089     QString origName;
00090     // Thumbnail file name for current item
00091     QString thumbName;
00092     // Size of thumbnail
00093     int width;
00094     int height;
00095     // Unscaled size of thumbnail (128 or 256 if cache is enabled)
00096     int cacheWidth;
00097     int cacheHeight;
00098     // Whether the thumbnail should be scaled
00099     bool bScale;
00100     // Whether we should save the thumbnail
00101     bool bSave;
00102     bool ignoreMaximumSize;
00103     int sequenceIndex;
00104     bool succeeded;
00105     // If the file to create a thumb for was a temp file, this is its name
00106     QString tempName;
00107     // Over that, it's too much
00108     KIO::filesize_t maximumSize;
00109     // the size for the icon overlay
00110     int iconSize;
00111     // the transparency of the blended mimetype icon
00112     int iconAlpha;
00113     // Shared memory segment Id. The segment is allocated to a size
00114     // of extent x extent x 4 (32 bit image) on first need.
00115     int shmid;
00116     // And the data area
00117     uchar *shmaddr;
00118     // Root of thumbnail cache
00119     QString thumbRoot;
00120 
00121     void getOrCreateThumbnail();
00122     bool statResultThumbnail();
00123     void createThumbnail( const QString& );
00124     void determineNextFile();
00125     void emitPreview(const QImage &thumb);
00126 
00127     void startPreview();
00128     void slotThumbData(KIO::Job *, const QByteArray &);
00129 
00130     Q_DECLARE_PUBLIC(PreviewJob)
00131 };
00132 
00133 PreviewJob::PreviewJob( const KFileItemList &items, int width, int height,
00134     int iconSize, int iconAlpha, bool scale, bool save,
00135     const QStringList *enabledPlugins )
00136     : KIO::Job(*new PreviewJobPrivate)
00137 {
00138     Q_D(PreviewJob);
00139     d->tOrig = 0;
00140     d->shmid = -1;
00141     d->shmaddr = 0;
00142     d->initialItems = items;
00143     d->enabledPlugins = enabledPlugins ? *enabledPlugins : QStringList();
00144     d->width = width;
00145     d->height = height ? height : width;
00146     d->cacheWidth = d->width;
00147     d->cacheHeight = d->height;
00148     d->iconSize = iconSize;
00149     d->iconAlpha = iconAlpha;
00150     d->bScale = scale;
00151     d->bSave = save && scale;
00152     d->succeeded = false;
00153     d->thumbRoot = QDir::homePath() + "/.thumbnails/";
00154     d->ignoreMaximumSize = false;
00155     d->sequenceIndex = 0;
00156 
00157     // Return to event loop first, determineNextFile() might delete this;
00158     QTimer::singleShot(0, this, SLOT(startPreview()));
00159 }
00160 
00161 PreviewJob::~PreviewJob()
00162 {
00163 #ifdef Q_OS_UNIX
00164     Q_D(PreviewJob);
00165     if (d->shmaddr) {
00166         shmdt((char*)d->shmaddr);
00167         shmctl(d->shmid, IPC_RMID, 0);
00168     }
00169 #endif
00170 }
00171 
00172 void PreviewJobPrivate::startPreview()
00173 {
00174     Q_Q(PreviewJob);
00175     // Load the list of plugins to determine which mimetypes are supported
00176     const KService::List plugins = KServiceTypeTrader::self()->query("ThumbCreator");
00177     QMap<QString, KService::Ptr> mimeMap;
00178 
00179     for (KService::List::ConstIterator it = plugins.constBegin(); it != plugins.constEnd(); ++it) {
00180         if (enabledPlugins.isEmpty() || enabledPlugins.contains((*it)->desktopEntryName()))
00181     {
00182         const QStringList mimeTypes = (*it)->serviceTypes();
00183         for (QStringList::ConstIterator mt = mimeTypes.constBegin(); mt != mimeTypes.constEnd(); ++mt)
00184             mimeMap.insert(*mt, *it);
00185     }
00186     }
00187 
00188     // Look for images and store the items in our todo list :)
00189     bool bNeedCache = false;
00190     KFileItemList::const_iterator kit = initialItems.constBegin();
00191     const KFileItemList::const_iterator kend = initialItems.constEnd();
00192     for ( ; kit != kend; ++kit )
00193     {
00194         PreviewItem item;
00195         item.item = *kit;
00196         const QString mimeType = item.item.mimetype();
00197         QMap<QString, KService::Ptr>::ConstIterator plugin = mimeMap.constFind(mimeType);
00198         if (plugin == mimeMap.constEnd())
00199 
00200         {
00201             QString groupMimeType = mimeType;
00202             groupMimeType.replace(QRegExp("/.*"), "/*");
00203             plugin = mimeMap.constFind(groupMimeType);
00204 
00205             if (plugin == mimeMap.constEnd())
00206             {
00207                 // check mime type inheritance, resolve aliases
00208                 const KMimeType::Ptr mimeInfo = KMimeType::mimeType(mimeType, KMimeType::ResolveAliases);
00209                 if (mimeInfo) {
00210                     const QStringList parentMimeTypes = mimeInfo->allParentMimeTypes();
00211                     Q_FOREACH(const QString& parentMimeType, parentMimeTypes) {
00212                         plugin = mimeMap.constFind(parentMimeType);
00213                         if (plugin != mimeMap.constEnd()) break;
00214                     }
00215                 }
00216             }
00217 #if 0 // KDE4: should be covered by inheritance above, all text mimetypes inherit from text/plain
00218             // if that's not enough, we need to invent something else
00219             if (plugin == mimeMap.end())
00220             {
00221                 // check X-KDE-Text property
00222                 KMimeType::Ptr mimeInfo = KMimeType::mimeType(mimeType);
00223                 QVariant textProperty = mimeInfo->property("X-KDE-text");
00224                 if (textProperty.isValid() && textProperty.type() == QVariant::Bool)
00225                 {
00226                     if (textProperty.toBool())
00227                     {
00228                         plugin = mimeMap.find("text/plain");
00229                         if (plugin == mimeMap.end())
00230                         {
00231                             plugin = mimeMap.find( "text/*" );
00232                         }
00233                     }
00234                 }
00235             }
00236 #endif
00237         }
00238 
00239         if (plugin != mimeMap.constEnd())
00240         {
00241             item.plugin = *plugin;
00242             items.append(item);
00243             if (!bNeedCache && bSave &&
00244                 ((*kit).url().protocol() != "file" ||
00245                  !(*kit).url().directory( KUrl::AppendTrailingSlash ).startsWith(thumbRoot)) &&
00246                 (*plugin)->property("CacheThumbnail").toBool())
00247                 bNeedCache = true;
00248         }
00249         else
00250         {
00251             emit q->failed( *kit );
00252         }
00253     }
00254 
00255   // Read configuration value for the maximum allowed size
00256     maximumSize = PreviewJob::maximumFileSize();
00257 
00258     if (bNeedCache)
00259     {
00260         if (width <= 128 && height <= 128) cacheWidth = cacheHeight = 128;
00261         else cacheWidth = cacheHeight = 256;
00262         thumbPath = thumbRoot + (cacheWidth == 128 ? "normal/" : "large/");
00263         KStandardDirs::makeDir(thumbPath, 0700);
00264     }
00265     else
00266         bSave = false;
00267 
00268     initialItems.clear();
00269     determineNextFile();
00270 }
00271 
00272 void PreviewJob::removeItem( const KUrl& url )
00273 {
00274     Q_D(PreviewJob);
00275     for (QLinkedList<PreviewItem>::Iterator it = d->items.begin(); it != d->items.end(); ++it)
00276         if ((*it).item.url() == url)
00277         {
00278             d->items.erase(it);
00279             break;
00280         }
00281 
00282     if (d->currentItem.item.url() == url)
00283     {
00284         KJob* job = subjobs().first();
00285         job->kill();
00286         removeSubjob( job );
00287         d->determineNextFile();
00288     }
00289 }
00290 
00291 void KIO::PreviewJob::setSequenceIndex(int index) {
00292     d_func()->sequenceIndex = index;
00293 }
00294 
00295 int KIO::PreviewJob::sequenceIndex() const {
00296     return d_func()->sequenceIndex;
00297 }
00298 
00299 void PreviewJob::setIgnoreMaximumSize(bool ignoreSize)
00300 {
00301     d_func()->ignoreMaximumSize = ignoreSize;
00302 }
00303 
00304 void PreviewJobPrivate::determineNextFile()
00305 {
00306     Q_Q(PreviewJob);
00307     if (!currentItem.item.isNull())
00308     {
00309         if (!succeeded)
00310             emit q->failed( currentItem.item );
00311     }
00312     // No more items ?
00313     if ( items.isEmpty() )
00314     {
00315         q->emitResult();
00316         return;
00317     }
00318     else
00319     {
00320         // First, stat the orig file
00321         state = PreviewJobPrivate::STATE_STATORIG;
00322         currentItem = items.first();
00323         succeeded = false;
00324         items.removeFirst();
00325         KIO::Job *job = KIO::stat( currentItem.item.url(), KIO::HideProgressInfo );
00326         job->addMetaData( "no-auth-prompt", "true" );
00327         q->addSubjob(job);
00328     }
00329 }
00330 
00331 void PreviewJob::slotResult( KJob *job )
00332 {
00333     Q_D(PreviewJob);
00334 
00335     removeSubjob(job);
00336     Q_ASSERT ( !hasSubjobs() ); // We should have only one job at a time ...
00337     switch ( d->state )
00338     {
00339         case PreviewJobPrivate::STATE_STATORIG:
00340         {
00341             if (job->error()) // that's no good news...
00342             {
00343                 // Drop this one and move on to the next one
00344                 d->determineNextFile();
00345                 return;
00346             }
00347             const KIO::UDSEntry entry = static_cast<KIO::StatJob*>(job)->statResult();
00348             d->tOrig = entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, 0 );
00349             if ( !d->ignoreMaximumSize &&
00350                  (KIO::filesize_t)entry.numberValue( KIO::UDSEntry::UDS_SIZE, 0 ) > d->maximumSize &&
00351                  !d->currentItem.plugin->property("IgnoreMaximumSize").toBool()
00352                 ) {
00353                 d->determineNextFile();
00354                 return;
00355             }
00356 
00357             if ( !d->currentItem.plugin->property( "CacheThumbnail" ).toBool()  || d->sequenceIndex )
00358             {
00359                 // This preview will not be cached, no need to look for a saved thumbnail
00360                 // Just create it, and be done
00361                 d->getOrCreateThumbnail();
00362                 return;
00363             }
00364 
00365             if ( d->statResultThumbnail() )
00366                 return;
00367 
00368             d->getOrCreateThumbnail();
00369             return;
00370         }
00371         case PreviewJobPrivate::STATE_GETORIG:
00372         {
00373             if (job->error())
00374             {
00375                 d->determineNextFile();
00376                 return;
00377             }
00378 
00379             d->createThumbnail( static_cast<KIO::FileCopyJob*>(job)->destUrl().toLocalFile() );
00380             return;
00381         }
00382         case PreviewJobPrivate::STATE_CREATETHUMB:
00383         {
00384             if (!d->tempName.isEmpty())
00385             {
00386                 QFile::remove(d->tempName);
00387                 d->tempName.clear();
00388             }
00389             d->determineNextFile();
00390             return;
00391         }
00392     }
00393 }
00394 
00395 bool PreviewJobPrivate::statResultThumbnail()
00396 {
00397     if ( thumbPath.isEmpty() )
00398         return false;
00399 
00400     KUrl url = currentItem.item.url();
00401     // Don't include the password if any
00402     url.setPass(QString());
00403     origName = url.url();
00404 
00405     KMD5 md5( QFile::encodeName( origName ) );
00406     thumbName = QFile::encodeName( md5.hexDigest() ) + ".png";
00407 
00408     QImage thumb;
00409     if ( !thumb.load( thumbPath + thumbName ) ) return false;
00410 
00411     if ( thumb.text( "Thumb::URI", 0 ) != origName ||
00412          thumb.text( "Thumb::MTime", 0 ).toInt() != tOrig ) return false;
00413 
00414     // Found it, use it
00415     emitPreview( thumb );
00416     succeeded = true;
00417     determineNextFile();
00418     return true;
00419 }
00420 
00421 
00422 void PreviewJobPrivate::getOrCreateThumbnail()
00423 {
00424     Q_Q(PreviewJob);
00425     // We still need to load the orig file ! (This is getting tedious) :)
00426     const KFileItem& item = currentItem.item;
00427     const QString localPath = item.localPath();
00428     if ( !localPath.isEmpty() )
00429         createThumbnail( localPath );
00430     else
00431     {
00432         state = PreviewJobPrivate::STATE_GETORIG;
00433         KTemporaryFile localFile;
00434         localFile.setAutoRemove(false);
00435         localFile.open();
00436         KUrl localURL;
00437         localURL.setPath( tempName = localFile.fileName() );
00438         const KUrl currentURL = item.url();
00439         KIO::Job * job = KIO::file_copy( currentURL, localURL, -1, KIO::Overwrite | KIO::HideProgressInfo /* No GUI */ );
00440         job->addMetaData("thumbnail","1");
00441         q->addSubjob(job);
00442     }
00443 }
00444 
00445 void PreviewJobPrivate::createThumbnail( const QString &pixPath )
00446 {
00447     Q_Q(PreviewJob);
00448     state = PreviewJobPrivate::STATE_CREATETHUMB;
00449     KUrl thumbURL;
00450     thumbURL.setProtocol("thumbnail");
00451     thumbURL.setPath(pixPath);
00452     KIO::TransferJob *job = KIO::get(thumbURL, NoReload, HideProgressInfo);
00453     q->addSubjob(job);
00454     q->connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), SLOT(slotThumbData(KIO::Job *, const QByteArray &)));
00455     bool save = bSave && currentItem.plugin->property("CacheThumbnail").toBool() && !sequenceIndex;
00456     job->addMetaData("mimeType", currentItem.item.mimetype());
00457     job->addMetaData("width", QString().setNum(save ? cacheWidth : width));
00458     job->addMetaData("height", QString().setNum(save ? cacheHeight : height));
00459     job->addMetaData("iconSize", QString().setNum(save ? 64 : iconSize));
00460     job->addMetaData("iconAlpha", QString().setNum(iconAlpha));
00461     job->addMetaData("plugin", currentItem.plugin->library());
00462     if(sequenceIndex)
00463         job->addMetaData("sequence-index", QString().setNum(sequenceIndex));
00464 
00465 #ifdef Q_OS_UNIX
00466     if (shmid == -1)
00467     {
00468         if (shmaddr) {
00469             shmdt((char*)shmaddr);
00470             shmctl(shmid, IPC_RMID, 0);
00471         }
00472         shmid = shmget(IPC_PRIVATE, cacheWidth * cacheHeight * 4, IPC_CREAT|0600);
00473         if (shmid != -1)
00474         {
00475             shmaddr = (uchar *)(shmat(shmid, 0, SHM_RDONLY));
00476             if (shmaddr == (uchar *)-1)
00477             {
00478                 shmctl(shmid, IPC_RMID, 0);
00479                 shmaddr = 0;
00480                 shmid = -1;
00481             }
00482         }
00483         else
00484             shmaddr = 0;
00485     }
00486     if (shmid != -1)
00487         job->addMetaData("shmid", QString().setNum(shmid));
00488 #endif
00489 }
00490 
00491 void PreviewJobPrivate::slotThumbData(KIO::Job *, const QByteArray &data)
00492 {
00493     bool save = bSave &&
00494                 currentItem.plugin->property("CacheThumbnail").toBool() &&
00495                 (currentItem.item.url().protocol() != "file" ||
00496                  !currentItem.item.url().directory( KUrl::AppendTrailingSlash ).startsWith(thumbRoot)) && !sequenceIndex;
00497     QImage thumb;
00498 #ifdef Q_OS_UNIX
00499     if (shmaddr)
00500     {
00501         // Keep this in sync with kdebase/kioslave/thumbnail.cpp
00502         QDataStream str(data);
00503         int width, height;
00504         quint8 iFormat;
00505         str >> width >> height >> iFormat;
00506         QImage::Format format = static_cast<QImage::Format>( iFormat );
00507         thumb = QImage(shmaddr, width, height, format ).copy();
00508     }
00509     else
00510 #endif
00511         thumb.loadFromData(data);
00512 
00513     if (thumb.isNull()) {
00514         QDataStream s(data);
00515         s >> thumb;
00516     }
00517 
00518     QString tempFileName;
00519     bool savedCorrectly = false;
00520     if (save)
00521     {
00522         thumb.setText("Thumb::URI", origName);
00523         thumb.setText("Thumb::MTime", QString::number(tOrig));
00524         thumb.setText("Thumb::Size", number(currentItem.item.size()));
00525         thumb.setText("Thumb::Mimetype", currentItem.item.mimetype());
00526         thumb.setText("Software", "KDE Thumbnail Generator");
00527         KTemporaryFile temp;
00528         temp.setPrefix(thumbPath + "kde-tmp-");
00529         temp.setSuffix(".png");
00530         temp.setAutoRemove(false);
00531         if (temp.open()) //Only try to write out the thumbnail if we
00532         {                //actually created the temp file.
00533             tempFileName = temp.fileName();
00534             savedCorrectly = thumb.save(tempFileName, "PNG");
00535         }
00536     }
00537     if(savedCorrectly)
00538     {
00539         Q_ASSERT(!tempFileName.isEmpty());
00540         KDE::rename(tempFileName, thumbPath + thumbName);
00541     }
00542     emitPreview( thumb );
00543     succeeded = true;
00544 }
00545 
00546 void PreviewJobPrivate::emitPreview(const QImage &thumb)
00547 {
00548     Q_Q(PreviewJob);
00549     QPixmap pix;
00550     if (thumb.width() > width || thumb.height() > height)
00551         pix = QPixmap::fromImage( thumb.scaled(QSize(width, height), Qt::KeepAspectRatio, Qt::SmoothTransformation) );
00552     else
00553         pix = QPixmap::fromImage( thumb );
00554     emit q->gotPreview(currentItem.item, pix);
00555 }
00556 
00557 QStringList PreviewJob::availablePlugins()
00558 {
00559     QStringList result;
00560     const KService::List plugins = KServiceTypeTrader::self()->query("ThumbCreator");
00561     for (KService::List::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00562         if (!result.contains((*it)->desktopEntryName()))
00563             result.append((*it)->desktopEntryName());
00564     return result;
00565 }
00566 
00567 QStringList PreviewJob::supportedMimeTypes()
00568 {
00569     QStringList result;
00570     const KService::List plugins = KServiceTypeTrader::self()->query("ThumbCreator");
00571     for (KService::List::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00572         result += (*it)->serviceTypes();
00573     return result;
00574 }
00575 
00576 PreviewJob *KIO::filePreview( const KFileItemList &items, int width, int height,
00577     int iconSize, int iconAlpha, bool scale, bool save,
00578     const QStringList *enabledPlugins )
00579 {
00580     return new PreviewJob(items, width, height, iconSize, iconAlpha,
00581                           scale, save, enabledPlugins);
00582 }
00583 
00584 PreviewJob *KIO::filePreview( const KUrl::List &items, int width, int height,
00585     int iconSize, int iconAlpha, bool scale, bool save,
00586     const QStringList *enabledPlugins )
00587 {
00588     KFileItemList fileItems;
00589     for (KUrl::List::ConstIterator it = items.begin(); it != items.end(); ++it) {
00590         Q_ASSERT( (*it).isValid() ); // please call us with valid urls only
00591         fileItems.append(KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it, true));
00592     }
00593     return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
00594                           scale, save, enabledPlugins);
00595 }
00596 
00597 KIO::filesize_t PreviewJob::maximumFileSize()
00598 {
00599     KConfigGroup cg( KGlobal::config(), "PreviewSettings" );
00600     return cg.readEntry( "MaximumSize", 5*1024*1024LL /* 5MB */ );
00601 }
00602 
00603 #include "previewjob.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
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
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