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

KIO

kurlcompletion.cpp
Go to the documentation of this file.
00001 /* -*- indent-tabs-mode: t; tab-width: 4; c-basic-offset:4 -*-
00002 
00003    This file is part of the KDE libraries
00004    Copyright (C) 2000 David Smith <dsmith@algonet.se>
00005    Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
00006 
00007    This class was inspired by a previous KUrlCompletion by
00008    Henner Zeller <zeller@think.de>
00009 
00010    This library is free software; you can redistribute it and/or
00011    modify it under the terms of the GNU Library General Public
00012    License as published by the Free Software Foundation; either
00013    version 2 of the License, or (at your option) any later version.
00014 
00015    This library is distributed in the hope that it will be useful,
00016    but WITHOUT ANY WARRANTY; without even the implied warranty of
00017    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00018    Library General Public License for more details.
00019 
00020    You should have received a copy of the GNU Library General Public License
00021    along with this library; see the file COPYING.LIB.   If not, write to
00022    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00023    Boston, MA 02110-1301, USA.
00024 */
00025 
00026 #include "kurlcompletion.h"
00027 
00028 #include <config.h>
00029 
00030 #include <stdlib.h>
00031 #include <assert.h>
00032 #include <limits.h>
00033 
00034 #include <QtCore/QCoreApplication>
00035 #include <QtCore/QMutableStringListIterator>
00036 #include <QtCore/QRegExp>
00037 #include <QtCore/QTimer>
00038 #include <QtCore/QDir>
00039 #include <QtCore/QDirIterator>
00040 #include <QtCore/QFile>
00041 #include <QtCore/QTextIStream>
00042 #include <QtCore/QThread>
00043 #include <QtGui/QActionEvent>
00044 
00045 #include <kauthorized.h>
00046 #include <kdebug.h>
00047 #include <kurl.h>
00048 #include <kio/job.h>
00049 #include <kprotocolmanager.h>
00050 #include <kconfig.h>
00051 #include <kglobal.h>
00052 #include <kglobalsettings.h>
00053 #include <kde_file.h>
00054 
00055 #include <sys/types.h>
00056 #include <dirent.h>
00057 #include <unistd.h>
00058 #include <sys/stat.h>
00059 #include <pwd.h>
00060 #include <time.h>
00061 #include <sys/param.h>
00062 #include <kconfiggroup.h>
00063 
00064 #ifdef Q_WS_WIN
00065 #include <kkernel_win.h>
00066 #endif
00067 
00068 static bool expandTilde(QString&);
00069 static bool expandEnv(QString&);
00070 
00071 static QString unescape(const QString& text);
00072 
00073 // Permission mask for files that are executable by
00074 // user, group or other
00075 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH)
00076 
00077 // Constants for types of completion
00078 enum ComplType {CTNone = 0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo};
00079 
00080 class CompletionThread;
00081 
00084 // KUrlCompletionPrivate
00085 //
00086 class KUrlCompletionPrivate
00087 {
00088 public:
00089     KUrlCompletionPrivate(KUrlCompletion* parent)
00090         : q(parent),
00091           url_auto_completion(true),
00092           userListThread(0),
00093           dirListThread(0) {
00094     }
00095 
00096     ~KUrlCompletionPrivate();
00097 
00098     void _k_slotEntries(KIO::Job*, const KIO::UDSEntryList&);
00099     void _k_slotIOFinished(KJob*);
00100 
00101     class MyURL;
00102     bool userCompletion(const MyURL& url, QString* match);
00103     bool envCompletion(const MyURL& url, QString* match);
00104     bool exeCompletion(const MyURL& url, QString* match);
00105     bool fileCompletion(const MyURL& url, QString* match);
00106     bool urlCompletion(const MyURL& url, QString* match);
00107 
00108     bool isAutoCompletion();
00109 
00110     // List the next dir in m_dirs
00111     QString listDirectories(const QStringList&,
00112                              const QString&,
00113                              bool only_exe = false,
00114                              bool only_dir = false,
00115                              bool no_hidden = false,
00116                              bool stat_files = true);
00117 
00118     void listUrls(const QList<KUrl> &urls,
00119                    const QString& filter = QString(),
00120                    bool only_exe = false,
00121                    bool no_hidden = false);
00122 
00123     void addMatches(const QStringList&);
00124     QString finished();
00125 
00126     void init();
00127 
00128     void setListedUrl(int compl_type /* enum ComplType */,
00129                        const QString& dir = QString(),
00130                        const QString& filter = QString(),
00131                        bool no_hidden = false);
00132 
00133     bool isListedUrl(int compl_type /* enum ComplType */,
00134                       const QString& dir = QString(),
00135                       const QString& filter = QString(),
00136                       bool no_hidden = false);
00137 
00138     KUrlCompletion* q;
00139     QList<KUrl> list_urls;
00140 
00141     bool onlyLocalProto;
00142 
00143     // urlCompletion() in Auto/Popup mode?
00144     bool url_auto_completion;
00145 
00146     // Append '/' to directories in Popup mode?
00147     // Doing that stat's all files and is slower
00148     bool popup_append_slash;
00149 
00150     // Keep track of currently listed files to avoid reading them again
00151     QString last_path_listed;
00152     QString last_file_listed;
00153     QString last_prepend;
00154     int last_compl_type;
00155     int last_no_hidden;
00156 
00157     QString cwd; // "current directory" = base dir for completion
00158 
00159     KUrlCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion
00160     bool replace_env;
00161     bool replace_home;
00162     bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path
00163 
00164     KIO::ListJob* list_job; // kio job to list directories
00165 
00166     QString prepend; // text to prepend to listed items
00167     QString compl_text; // text to pass on to KCompletion
00168 
00169     // Filters for files read with  kio
00170     bool list_urls_only_exe; // true = only list executables
00171     bool list_urls_no_hidden;
00172     QString list_urls_filter; // filter for listed files
00173 
00174     CompletionThread* userListThread;
00175     CompletionThread* dirListThread;
00176 };
00177 
00183 class CompletionMatchEvent : public QEvent
00184 {
00185 public:
00186     CompletionMatchEvent(CompletionThread* thread) :
00187         QEvent(uniqueType()),
00188         m_completionThread(thread)
00189     {}
00190 
00191     CompletionThread* completionThread() const {
00192         return m_completionThread;
00193     }
00194     static Type uniqueType() {
00195         return Type(User + 61080);
00196     }
00197 
00198 private:
00199     CompletionThread* m_completionThread;
00200 };
00201 
00202 class CompletionThread : public QThread
00203 {
00204 protected:
00205     CompletionThread(KUrlCompletionPrivate* receiver) :
00206         QThread(),
00207         m_prepend(receiver->prepend),
00208         m_complete_url(receiver->complete_url),
00209         m_receiver(receiver),
00210         m_terminationRequested(false)
00211     {}
00212 
00213 public:
00214     void requestTermination() {
00215         m_terminationRequested = true;
00216     }
00217     QStringList matches() const {
00218         return m_matches;
00219     }
00220 
00221 protected:
00222     void addMatch(const QString& match) {
00223         m_matches.append(match);
00224     }
00225     bool terminationRequested() const {
00226         return m_terminationRequested;
00227     }
00228     void done() {
00229         if (!m_terminationRequested)
00230             qApp->postEvent(m_receiver->q, new CompletionMatchEvent(this));
00231         else
00232             deleteLater();
00233     }
00234 
00235     const QString m_prepend;
00236     const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path
00237 
00238 private:
00239     KUrlCompletionPrivate* m_receiver;
00240     QStringList m_matches;
00241     bool m_terminationRequested;
00242 };
00243 
00249 class UserListThread : public CompletionThread
00250 {
00251 public:
00252     UserListThread(KUrlCompletionPrivate* receiver) :
00253         CompletionThread(receiver)
00254     {}
00255 
00256 protected:
00257     virtual void run() {
00258         static const QChar tilde = '~';
00259 
00260         // we don't need to handle prepend here, right? ~user is always at pos 0
00261         assert(m_prepend.isEmpty());
00262         struct passwd* pw;
00263         while ((pw = ::getpwent()) && !terminationRequested())
00264             addMatch(tilde + QString::fromLocal8Bit(pw->pw_name));
00265 
00266         ::endpwent();
00267 
00268         addMatch(QString(tilde));
00269 
00270         done();
00271     }
00272 };
00273 
00274 class DirectoryListThread : public CompletionThread
00275 {
00276 public:
00277     DirectoryListThread(KUrlCompletionPrivate* receiver,
00278                          const QStringList& dirList,
00279                          const QString& filter,
00280                          bool onlyExe,
00281                          bool onlyDir,
00282                          bool noHidden,
00283                          bool appendSlashToDir) :
00284         CompletionThread(receiver),
00285         m_dirList(dirList),
00286         m_filter(filter),
00287         m_onlyExe(onlyExe),
00288         m_onlyDir(onlyDir),
00289         m_noHidden(noHidden),
00290         m_appendSlashToDir(appendSlashToDir)
00291     {}
00292 
00293     virtual void run();
00294 
00295 private:
00296     QStringList m_dirList;
00297     QString m_filter;
00298     bool m_onlyExe;
00299     bool m_onlyDir;
00300     bool m_noHidden;
00301     bool m_appendSlashToDir;
00302 };
00303 
00304 void DirectoryListThread::run()
00305 {
00306     // Thread safety notes:
00307     //
00308     // There very possibly may be thread safety issues here, but I've done a check
00309     // of all of the things that would seem to be problematic.  Here are a few
00310     // things that I have checked to be safe here (some used indirectly):
00311     //
00312     // QDir::currentPath(), QDir::setCurrent(), QFile::decodeName(), QFile::encodeName()
00313     // QString::fromLocal8Bit(), QString::toLocal8Bit(), QTextCodec::codecForLocale()
00314     //
00315     // Also see (for POSIX functions):
00316     // http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
00317 
00318     // kDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ", m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size();
00319 
00320     QStringList::ConstIterator end = m_dirList.constEnd();
00321     for (QStringList::ConstIterator it = m_dirList.constBegin();
00322             it != end && !terminationRequested();
00323             ++it) {
00324         // kDebug() << "Scanning directory" << *it;
00325 
00326         // A trick from KIO that helps performance by a little bit:
00327         // chdir to the directory so we won't have to deal with full paths
00328         // with stat()
00329 
00330         QString path = QDir::currentPath();
00331         QDir::setCurrent(*it);
00332 
00333         QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot;
00334 
00335         if (m_onlyExe)
00336             iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable);
00337         else if (m_onlyDir)
00338             iterator_filter |= QDir::Dirs;
00339         else
00340             iterator_filter |= (QDir::Dirs | QDir::Files);
00341 
00342         QDirIterator current_dir_iterator(*it, iterator_filter);
00343 
00344         while (current_dir_iterator.hasNext()) {
00345             current_dir_iterator.next();
00346 
00347             QFileInfo file_info = current_dir_iterator.fileInfo();
00348             const QString file_name = file_info.fileName();
00349 
00350             //kDebug() << "Found" << file_name;
00351 
00352             if (m_filter.isEmpty() || file_name.startsWith(m_filter)) {
00353 
00354                 QString toAppend = file_name;
00355                 // Add '/' to directories
00356                 if (m_appendSlashToDir && file_info.isDir())
00357                     toAppend.append(QLatin1Char('/'));
00358 
00359                 if (m_complete_url) {
00360                     KUrl url(m_prepend);
00361                     url.addPath(toAppend);
00362                     addMatch(url.prettyUrl());
00363                 } else {
00364                     addMatch(m_prepend + toAppend);
00365                 }
00366             }
00367         }
00368 
00369         // chdir to the original directory
00370         QDir::setCurrent(path);
00371     }
00372 
00373     done();
00374 }
00375 
00376 KUrlCompletionPrivate::~KUrlCompletionPrivate()
00377 {
00378     if (userListThread)
00379         userListThread->requestTermination();
00380     if (dirListThread)
00381         dirListThread->requestTermination();
00382 }
00383 
00386 // MyURL - wrapper for KUrl with some different functionality
00387 //
00388 
00389 class KUrlCompletionPrivate::MyURL
00390 {
00391 public:
00392     MyURL(const QString& url, const QString& cwd);
00393     MyURL(const MyURL& url);
00394     ~MyURL();
00395 
00396     KUrl kurl() const {
00397         return m_kurl;
00398     }
00399 
00400     QString protocol() const {
00401         return m_kurl.protocol();
00402     }
00403     // The directory with a trailing '/'
00404     QString dir() const {
00405         return m_kurl.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
00406     }
00407     QString file() const {
00408         return m_kurl.fileName(KUrl::ObeyTrailingSlash);
00409     }
00410 
00411     // The initial, unparsed, url, as a string.
00412     QString url() const {
00413         return m_url;
00414     }
00415 
00416     // Is the initial string a URL, or just a path (whether absolute or relative)
00417     bool isURL() const {
00418         return m_isURL;
00419     }
00420 
00421     void filter(bool replace_user_dir, bool replace_env);
00422 
00423 private:
00424     void init(const QString& url, const QString& cwd);
00425 
00426     KUrl m_kurl;
00427     QString m_url;
00428     bool m_isURL;
00429 };
00430 
00431 KUrlCompletionPrivate::MyURL::MyURL(const QString& _url, const QString& cwd)
00432 {
00433     init(_url, cwd);
00434 }
00435 
00436 KUrlCompletionPrivate::MyURL::MyURL(const MyURL& _url)
00437     : m_kurl(_url.m_kurl)
00438 {
00439     m_url = _url.m_url;
00440     m_isURL = _url.m_isURL;
00441 }
00442 
00443 void KUrlCompletionPrivate::MyURL::init(const QString& _url, const QString& cwd)
00444 {
00445     // Save the original text
00446     m_url = _url;
00447 
00448     // Non-const copy
00449     QString url_copy = _url;
00450 
00451     // Special shortcuts for "man:" and "info:"
00452     if (url_copy.startsWith(QLatin1Char('#'))) {
00453         if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#'))
00454             url_copy.replace(0, 2, QLatin1String("info:"));
00455         else
00456             url_copy.replace(0, 1, QLatin1String("man:"));
00457     }
00458 
00459     // Look for a protocol in 'url'
00460     QRegExp protocol_regex = QRegExp("^(?![A-Za-z]:)[^/\\s\\\\]*:");
00461 
00462     // Assume "file:" or whatever is given by 'cwd' if there is
00463     // no protocol.  (KUrl does this only for absolute paths)
00464     if (protocol_regex.indexIn(url_copy) == 0) {
00465         m_kurl = KUrl(url_copy);
00466         m_isURL = true;
00467     } else { // relative path or ~ or $something
00468         m_isURL = false;
00469         if (!QDir::isRelativePath(url_copy) ||
00470                 url_copy.startsWith(QLatin1Char('~')) ||
00471                 url_copy.startsWith(QLatin1Char('$'))) {
00472             m_kurl = KUrl();
00473             m_kurl.setPath(url_copy);
00474         } else {
00475             if (cwd.isEmpty()) {
00476                 m_kurl = KUrl(url_copy);
00477             } else {
00478                 m_kurl = KUrl(cwd);
00479                 m_kurl.addPath(url_copy);
00480             }
00481         }
00482     }
00483 }
00484 
00485 KUrlCompletionPrivate::MyURL::~MyURL()
00486 {
00487 }
00488 
00489 void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env)
00490 {
00491     QString d = dir() + file();
00492     if (replace_user_dir) expandTilde(d);
00493     if (replace_env) expandEnv(d);
00494     m_kurl.setPath(d);
00495 }
00496 
00499 // KUrlCompletion
00500 //
00501 
00502 KUrlCompletion::KUrlCompletion() : KCompletion(), d(new KUrlCompletionPrivate(this))
00503 {
00504     d->init();
00505 }
00506 
00507 
00508 KUrlCompletion::KUrlCompletion(Mode _mode)
00509     : KCompletion(),
00510       d(new KUrlCompletionPrivate(this))
00511 {
00512     d->init();
00513     setMode(_mode);
00514 }
00515 
00516 KUrlCompletion::~KUrlCompletion()
00517 {
00518     stop();
00519     delete d;
00520 }
00521 
00522 
00523 void KUrlCompletionPrivate::init()
00524 {
00525     cwd = QDir::homePath();
00526 
00527     replace_home = true;
00528     replace_env = true;
00529     last_no_hidden = false;
00530     last_compl_type = 0;
00531     list_job = 0L;
00532     mode = KUrlCompletion::FileCompletion;
00533 
00534     // Read settings
00535     KConfigGroup cg(KGlobal::config(), "URLCompletion");
00536 
00537     url_auto_completion = cg.readEntry("alwaysAutoComplete", true);
00538     popup_append_slash = cg.readEntry("popupAppendSlash", true);
00539     onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false);
00540 
00541     q->setIgnoreCase(true);
00542 }
00543 
00544 void KUrlCompletion::setDir(const QString& _dir)
00545 {
00546     d->cwd = _dir;
00547 }
00548 
00549 QString KUrlCompletion::dir() const
00550 {
00551     return d->cwd;
00552 }
00553 
00554 KUrlCompletion::Mode KUrlCompletion::mode() const
00555 {
00556     return d->mode;
00557 }
00558 
00559 void KUrlCompletion::setMode(Mode _mode)
00560 {
00561     d->mode = _mode;
00562 }
00563 
00564 bool KUrlCompletion::replaceEnv() const
00565 {
00566     return d->replace_env;
00567 }
00568 
00569 void KUrlCompletion::setReplaceEnv(bool replace)
00570 {
00571     d->replace_env = replace;
00572 }
00573 
00574 bool KUrlCompletion::replaceHome() const
00575 {
00576     return d->replace_home;
00577 }
00578 
00579 void KUrlCompletion::setReplaceHome(bool replace)
00580 {
00581     d->replace_home = replace;
00582 }
00583 
00584 /*
00585  * makeCompletion()
00586  *
00587  * Entry point for file name completion
00588  */
00589 QString KUrlCompletion::makeCompletion(const QString& text)
00590 {
00591     //kDebug() << text << "d->cwd=" << d->cwd;
00592 
00593     KUrlCompletionPrivate::MyURL url(text, d->cwd);
00594 
00595     d->compl_text = text;
00596 
00597     // Set d->prepend to the original URL, with the filename [and ref/query] stripped.
00598     // This is what gets prepended to the directory-listing matches.
00599     int toRemove = url.file().length() - url.kurl().query().length();
00600     if (url.kurl().hasRef())
00601         toRemove += url.kurl().ref().length() + 1;
00602     d->prepend = text.left(text.length() - toRemove);
00603     d->complete_url = url.isURL();
00604 
00605     QString aMatch;
00606 
00607     // Environment variables
00608     //
00609     if (d->replace_env && d->envCompletion(url, &aMatch))
00610         return aMatch;
00611 
00612     // User directories
00613     //
00614     if (d->replace_home && d->userCompletion(url, &aMatch))
00615         return aMatch;
00616 
00617     // Replace user directories and variables
00618     url.filter(d->replace_home, d->replace_env);
00619 
00620     //kDebug() << "Filtered: proto=" << url.protocol()
00621     //          << ", dir=" << url.dir()
00622     //          << ", file=" << url.file()
00623     //          << ", kurl url=" << *url.kurl();
00624 
00625     if (d->mode == ExeCompletion) {
00626         // Executables
00627         //
00628         if (d->exeCompletion(url, &aMatch))
00629             return aMatch;
00630 
00631         // KRun can run "man:" and "info:" etc. so why not treat them
00632         // as executables...
00633 
00634         if (d->urlCompletion(url, &aMatch))
00635             return aMatch;
00636     } else {
00637         // Local files, directories
00638         //
00639         if (d->fileCompletion(url, &aMatch))
00640             return aMatch;
00641 
00642         // All other...
00643         //
00644         if (d->urlCompletion(url, &aMatch))
00645             return aMatch;
00646     }
00647 
00648     d->setListedUrl(CTNone);
00649     stop();
00650 
00651     return QString();
00652 }
00653 
00654 /*
00655  * finished
00656  *
00657  * Go on and call KCompletion.
00658  * Called when all matches have been added
00659  */
00660 QString KUrlCompletionPrivate::finished()
00661 {
00662     if (last_compl_type == CTInfo)
00663         return q->KCompletion::makeCompletion(compl_text.toLower());
00664     else
00665         return q->KCompletion::makeCompletion(compl_text);
00666 }
00667 
00668 /*
00669  * isRunning
00670  *
00671  * Return true if either a KIO job or the DirLister
00672  * is running
00673  */
00674 bool KUrlCompletion::isRunning() const
00675 {
00676     return d->list_job || (d->dirListThread && !d->dirListThread->isFinished());
00677 }
00678 
00679 /*
00680  * stop
00681  *
00682  * Stop and delete a running KIO job or the DirLister
00683  */
00684 void KUrlCompletion::stop()
00685 {
00686     if (d->list_job) {
00687         d->list_job->kill();
00688         d->list_job = 0L;
00689     }
00690 
00691     if (d->dirListThread) {
00692         d->dirListThread->requestTermination();
00693         d->dirListThread = 0;
00694     }
00695 }
00696 
00697 /*
00698  * Keep track of the last listed directory
00699  */
00700 void KUrlCompletionPrivate::setListedUrl(int complType,
00701         const QString& directory,
00702         const QString& filter,
00703         bool no_hidden)
00704 {
00705     last_compl_type = complType;
00706     last_path_listed = directory;
00707     last_file_listed = filter;
00708     last_no_hidden = (int) no_hidden;
00709     last_prepend = prepend;
00710 }
00711 
00712 bool KUrlCompletionPrivate::isListedUrl(int complType,
00713         const QString& directory,
00714         const QString& filter,
00715         bool no_hidden)
00716 {
00717     return  last_compl_type == complType
00718             && (last_path_listed == directory
00719                 || (directory.isEmpty() && last_path_listed.isEmpty()))
00720             && (filter.startsWith (last_file_listed)
00721                 || (filter.isEmpty() && last_file_listed.isEmpty()))
00722             && last_no_hidden == (int) no_hidden
00723             && last_prepend == prepend; // e.g. relative path vs absolute
00724 }
00725 
00726 /*
00727  * isAutoCompletion
00728  *
00729  * Returns true if completion mode is Auto or Popup
00730  */
00731 bool KUrlCompletionPrivate::isAutoCompletion()
00732 {
00733     return q->completionMode() == KGlobalSettings::CompletionAuto
00734            || q->completionMode() == KGlobalSettings::CompletionPopup
00735            || q->completionMode() == KGlobalSettings::CompletionMan
00736            || q->completionMode() == KGlobalSettings::CompletionPopupAuto;
00737 }
00740 // User directories
00741 //
00742 
00743 bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
00744 {
00745     if (url.protocol() != QLatin1String("file")
00746             || !url.dir().isEmpty()
00747             || !url.file().startsWith(QLatin1Char('~')))
00748         return false;
00749 
00750     if (!isListedUrl(CTUser)) {
00751         q->stop();
00752         q->clear();
00753 
00754         if (!userListThread) {
00755             userListThread = new UserListThread(this);
00756             userListThread->start();
00757 
00758             // If the thread finishes quickly make sure that the results
00759             // are added to the first matching case.
00760 
00761             userListThread->wait(200);
00762             const QStringList l = userListThread->matches();
00763             addMatches(l);
00764         }
00765     }
00766     *pMatch = finished();
00767     return true;
00768 }
00769 
00772 // Environment variables
00773 //
00774 
00775 #ifndef Q_OS_WIN
00776 extern char** environ; // Array of environment variables
00777 #endif
00778 
00779 bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
00780 {
00781     if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$'))
00782         return false;
00783 
00784     if (!isListedUrl(CTEnv)) {
00785         q->stop();
00786         q->clear();
00787 
00788         char** env = environ;
00789 
00790         QString dollar = QLatin1String("$");
00791 
00792         QStringList l;
00793 
00794         while (*env) {
00795             QString s = QString::fromLocal8Bit(*env);
00796 
00797             int pos = s.indexOf(QLatin1Char('='));
00798 
00799             if (pos == -1)
00800                 pos = s.length();
00801 
00802             if (pos > 0)
00803                 l.append(prepend + dollar + s.left(pos));
00804 
00805             env++;
00806         }
00807 
00808         addMatches(l);
00809     }
00810 
00811     setListedUrl(CTEnv);
00812 
00813     *pMatch = finished();
00814     return true;
00815 }
00816 
00819 // Executables
00820 //
00821 
00822 bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
00823 {
00824     if (url.protocol() != QLatin1String("file"))
00825         return false;
00826 
00827     QString directory = unescape(url.dir());  // remove escapes
00828 
00829     // Find directories to search for completions, either
00830     //
00831     // 1. complete path given in url
00832     // 2. current directory (d->cwd)
00833     // 3. $PATH
00834     // 4. no directory at all
00835 
00836     QStringList dirList;
00837 
00838     if (!url.file().isEmpty()) {
00839         // $PATH
00840         dirList = QString::fromLocal8Bit(qgetenv("PATH")).split(
00841                       KPATH_SEPARATOR, QString::SkipEmptyParts);
00842 
00843         QStringList::Iterator it = dirList.begin();
00844 
00845         for (; it != dirList.end(); ++it)
00846             it->append(QLatin1Char('/'));
00847     } else if (!QDir::isRelativePath(directory)) {
00848         // complete path in url
00849         dirList.append(directory);
00850     } else if (!directory.isEmpty() && !cwd.isEmpty()) {
00851         // current directory
00852         dirList.append(cwd + QLatin1Char('/') + directory);
00853     }
00854 
00855     // No hidden files unless the user types "."
00856     bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.');
00857 
00858     // List files if needed
00859     //
00860     if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) {
00861         q->stop();
00862         q->clear();
00863 
00864         setListedUrl(CTExe, directory, url.file(), no_hidden_files);
00865 
00866         *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files);
00867     } else if (!q->isRunning()) {
00868         *pMatch = finished();
00869     } else {
00870         if (dirListThread)
00871             setListedUrl(CTExe, directory, url.file(), no_hidden_files);
00872         pMatch->clear();
00873     }
00874 
00875     return true;
00876 }
00877 
00880 // Local files
00881 //
00882 
00883 bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
00884 {
00885     if (url.protocol() != QLatin1String("file"))
00886         return false;
00887 
00888     QString directory = unescape(url.dir());
00889 
00890     if (url.url().length() && url.url().at(0) == QLatin1Char('.')) {
00891         if (url.url().length() == 1) {
00892             *pMatch = (q->completionMode() == KGlobalSettings::CompletionMan) ?
00893                       QLatin1String(".") :
00894                       QLatin1String("..");
00895             return true;
00896         } else if (url.url().length() == 2 && url.url().at(1) == QLatin1Char('.')) {
00897             *pMatch = QLatin1String("..");
00898             return true;
00899         }
00900     }
00901 
00902     //kDebug() << "fileCompletion" << url << "dir=" << dir;
00903 
00904     // Find directories to search for completions, either
00905     //
00906     // 1. complete path given in url
00907     // 2. current directory (d->cwd)
00908     // 3. no directory at all
00909 
00910     QStringList dirList;
00911 
00912     if (!QDir::isRelativePath(directory)) {
00913         // complete path in url
00914         dirList.append(directory);
00915     } else if (!cwd.isEmpty()) {
00916         // current directory
00917         QString dirToAdd = cwd;
00918         if (!directory.isEmpty()) {
00919             if (!cwd.endsWith('/'))
00920                 dirToAdd.append(QLatin1Char('/'));
00921             dirToAdd.append(directory);
00922         }
00923         dirList.append(dirToAdd);
00924     }
00925 
00926     // No hidden files unless the user types "."
00927     bool no_hidden_files = !url.file().startsWith(QLatin1Char('.'));
00928 
00929     // List files if needed
00930     //
00931     if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) {
00932         q->stop();
00933         q->clear();
00934 
00935         setListedUrl(CTFile, directory, QString(), no_hidden_files);
00936 
00937         // Append '/' to directories in Popup mode?
00938         bool append_slash = (popup_append_slash
00939                              && (q->completionMode() == KGlobalSettings::CompletionPopup ||
00940                                  q->completionMode() == KGlobalSettings::CompletionPopupAuto));
00941 
00942         bool only_dir = (mode == KUrlCompletion::DirCompletion);
00943 
00944         *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files,
00945                                    append_slash);
00946     } else if (!q->isRunning()) {
00947         *pMatch = finished();
00948     } else {
00949         pMatch->clear();
00950     }
00951 
00952     return true;
00953 }
00954 
00957 // URLs not handled elsewhere...
00958 //
00959 
00960 static bool isLocalProtocol(const QString& protocol)
00961 {
00962     return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local"));
00963 }
00964 
00965 bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
00966 {
00967     //kDebug() << *url.kurl();
00968     if (onlyLocalProto && isLocalProtocol(url.protocol()))
00969         return false;
00970 
00971     // Use d->cwd as base url in case url is not absolute
00972     KUrl url_dir = url.kurl();
00973     if (url_dir.isRelative() && !cwd.isEmpty()) {
00974         const KUrl url_cwd (cwd);
00975         // Create an URL with the directory to be listed
00976         url_dir = KUrl(url_cwd,  url_dir.url());
00977     }
00978 
00979     // url is malformed
00980     if (!url_dir.isValid())
00981         return false;
00982 
00983     // non local urls
00984     if (!isLocalProtocol(url.protocol())) {
00985         // url does not specify host
00986         if (url_dir.host().isEmpty())
00987             return false;
00988 
00989         // url does not specify a valid directory
00990         if (url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash).isEmpty())
00991             return false;
00992 
00993         // automatic completion is disabled
00994         if (isAutoCompletion() && !url_auto_completion)
00995             return false;
00996     }
00997 
00998     // url handler doesn't support listing
00999     if (!KProtocolManager::supportsListing(url_dir))
01000         return false;
01001 
01002     url_dir.setFileName(QString()); // not really nesseccary, but clear the filename anyway...
01003 
01004     // Remove escapes
01005     QString directory = unescape(url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash));
01006 
01007     url_dir.setPath(directory);
01008 
01009     // List files if needed
01010     //
01011     if (!isListedUrl(CTUrl, url_dir.prettyUrl(), url.file())) {
01012         q->stop();
01013         q->clear();
01014 
01015         setListedUrl(CTUrl, url_dir.prettyUrl(), QString());
01016 
01017         QList<KUrl> url_list;
01018         url_list.append(url_dir);
01019 
01020         listUrls(url_list, QString(), false);
01021 
01022         pMatch->clear();
01023     } else if (!q->isRunning()) {
01024         *pMatch = finished();
01025     } else {
01026         pMatch->clear();
01027     }
01028 
01029     return true;
01030 }
01031 
01034 // Directory and URL listing
01035 //
01036 
01037 /*
01038  * addMatches
01039  *
01040  * Called to add matches to KCompletion
01041  */
01042 void KUrlCompletionPrivate::addMatches(const QStringList& matchList)
01043 {
01044     q->insertItems(matchList);
01045 }
01046 
01047 /*
01048  * listDirectories
01049  *
01050  * List files starting with 'filter' in the given directories,
01051  * either using DirLister or listURLs()
01052  *
01053  * In either case, addMatches() is called with the listed
01054  * files, and eventually finished() when the listing is done
01055  *
01056  * Returns the match if available, or QString() if
01057  * DirLister timed out or using kio
01058  */
01059 QString KUrlCompletionPrivate::listDirectories(
01060     const QStringList& dirList,
01061     const QString& filter,
01062     bool only_exe,
01063     bool only_dir,
01064     bool no_hidden,
01065     bool append_slash_to_dir)
01066 {
01067     assert(!q->isRunning());
01068 
01069     if (qgetenv("KURLCOMPLETION_LOCAL_KIO").isEmpty()) {
01070 
01071         //kDebug() << "Listing (listDirectories):" << dirList << "filter=" << filter << "without KIO";
01072 
01073         // Don't use KIO
01074 
01075         if (dirListThread)
01076             dirListThread->requestTermination();
01077 
01078         QStringList dirs;
01079 
01080         QStringList::ConstIterator end = dirList.constEnd();
01081         for (QStringList::ConstIterator it = dirList.constBegin();
01082                 it != end;
01083                 ++it) {
01084             KUrl url;
01085             url.setPath(*it);
01086             if (KAuthorized::authorizeUrlAction(QLatin1String("list"), KUrl(), url))
01087                 dirs.append(*it);
01088         }
01089 
01090         dirListThread = new DirectoryListThread(this, dirs, filter, only_exe, only_dir,
01091                                                 no_hidden, append_slash_to_dir);
01092         dirListThread->start();
01093         dirListThread->wait(200);
01094         addMatches(dirListThread->matches());
01095 
01096         return finished();
01097     }
01098 
01099     // Use KIO
01100     //kDebug() << "Listing (listDirectories):" << dirList << "with KIO";
01101 
01102     QList<KUrl> url_list;
01103 
01104     QStringList::ConstIterator it = dirList.constBegin();
01105     QStringList::ConstIterator end = dirList.constEnd();
01106 
01107     for (; it != end; ++it) {
01108         url_list.append(KUrl(*it));
01109     }
01110 
01111     listUrls(url_list, filter, only_exe, no_hidden);
01112     // Will call addMatches() and finished()
01113 
01114     return QString();
01115 }
01116 
01117 /*
01118  * listURLs
01119  *
01120  * Use KIO to list the given urls
01121  *
01122  * addMatches() is called with the listed files
01123  * finished() is called when the listing is done
01124  */
01125 void KUrlCompletionPrivate::listUrls(
01126     const QList<KUrl> &urls,
01127     const QString& filter,
01128     bool only_exe,
01129     bool no_hidden)
01130 {
01131     assert(list_urls.isEmpty());
01132     assert(list_job == 0L);
01133 
01134     list_urls = urls;
01135     list_urls_filter = filter;
01136     list_urls_only_exe = only_exe;
01137     list_urls_no_hidden = no_hidden;
01138 
01139     //kDebug() << "Listing URLs:" << *urls[0] << ",...";
01140 
01141     // Start it off by calling _k_slotIOFinished
01142     //
01143     // This will start a new list job as long as there
01144     // are urls in d->list_urls
01145     //
01146     _k_slotIOFinished(0);
01147 }
01148 
01149 /*
01150  * _k_slotEntries
01151  *
01152  * Receive files listed by KIO and call addMatches()
01153  */
01154 void KUrlCompletionPrivate::_k_slotEntries(KIO::Job*, const KIO::UDSEntryList& entries)
01155 {
01156     QStringList matchList;
01157 
01158     KIO::UDSEntryList::ConstIterator it = entries.constBegin();
01159     const KIO::UDSEntryList::ConstIterator end = entries.constEnd();
01160 
01161     QString filter = list_urls_filter;
01162 
01163     int filter_len = filter.length();
01164 
01165     // Iterate over all files
01166     //
01167     for (; it != end; ++it) {
01168         const KIO::UDSEntry& entry = *it;
01169         const QString url = entry.stringValue(KIO::UDSEntry::UDS_URL);
01170 
01171         QString entry_name;
01172         if (!url.isEmpty()) {
01173             // kDebug() << "url:" << url;
01174             entry_name = KUrl(url).fileName();
01175         } else {
01176             entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
01177         }
01178 
01179         // kDebug() << "name:" << name;
01180 
01181         if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) &&
01182                 (list_urls_no_hidden ||
01183                  entry_name.length() == 1 ||
01184                  (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.'))))
01185             continue;
01186 
01187         const bool isDir = entry.isDir();
01188 
01189         if (mode == KUrlCompletion::DirCompletion && !isDir)
01190             continue;
01191 
01192         if (filter_len == 0 || entry_name.left(filter_len) == filter) {
01193 
01194             QString toAppend = entry_name;
01195 
01196             if (isDir)
01197                 toAppend.append(QLatin1Char('/'));
01198 
01199             if (!list_urls_only_exe ||
01200                     (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE)  // true if executable
01201                ) {
01202                 if (complete_url) {
01203                     KUrl url(prepend);
01204                     url.addPath(toAppend);
01205                     matchList.append(url.prettyUrl());
01206                 } else {
01207                     matchList.append(prepend + toAppend);
01208                 }
01209             }
01210         }
01211     }
01212 
01213     addMatches(matchList);
01214 }
01215 
01216 /*
01217  * _k_slotIOFinished
01218  *
01219  * Called when a KIO job is finished.
01220  *
01221  * Start a new list job if there are still urls in
01222  * list_urls, otherwise call finished()
01223  */
01224 void KUrlCompletionPrivate::_k_slotIOFinished(KJob* job)
01225 {
01226     assert(job == list_job); Q_UNUSED(job)
01227 
01228     if (list_urls.isEmpty()) {
01229 
01230         list_job = 0L;
01231 
01232         finished(); // will call KCompletion::makeCompletion()
01233 
01234     } else {
01235 
01236         KUrl kurl(list_urls.takeFirst());
01237 
01238 //      list_urls.removeAll( kurl );
01239 
01240 //      kDebug() << "Start KIO::listDir" << kurl;
01241 
01242         list_job = KIO::listDir(kurl, KIO::HideProgressInfo);
01243         list_job->addMetaData("no-auth-prompt", "true");
01244 
01245         assert(list_job);
01246 
01247         q->connect(list_job,
01248                     SIGNAL(result(KJob*)),
01249                     SLOT(_k_slotIOFinished(KJob*)));
01250 
01251         q->connect(list_job,
01252                     SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
01253                     SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList)));
01254     }
01255 }
01256 
01259 
01260 /*
01261  * postProcessMatch, postProcessMatches
01262  *
01263  * Called by KCompletion before emitting match() and matches()
01264  *
01265  * Append '/' to directories for file completion. This is
01266  * done here to avoid stat()'ing a lot of files
01267  */
01268 void KUrlCompletion::postProcessMatch(QString* pMatch) const
01269 {
01270 //  kDebug() << *pMatch;
01271 
01272     if (!pMatch->isEmpty()) {
01273 
01274         // Add '/' to directories in file completion mode
01275         // unless it has already been done
01276         if (d->last_compl_type == CTFile
01277                 && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) {
01278             QString copy;
01279 
01280             if (pMatch->startsWith(QLatin1String("file:")))
01281                 copy = KUrl(*pMatch).toLocalFile();
01282             else
01283                 copy = *pMatch;
01284 
01285             expandTilde(copy);
01286             expandEnv(copy);
01287 #ifdef Q_WS_WIN
01288             DWORD dwAttr = GetFileAttributesW((LPCWSTR) copy.utf16());
01289             if (dwAttr == INVALID_FILE_ATTRIBUTES) {
01290                 kDebug() << "Could not get file attribs ( "
01291                          << GetLastError()
01292                          << " ) for "
01293                          << copy;
01294             } else if ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
01295                 pMatch->append(QLatin1Char('/'));
01296 #else
01297             if (QDir::isRelativePath(copy))
01298                 copy.prepend(d->cwd + QLatin1Char('/'));
01299 
01300 //          kDebug() << "stat'ing" << copy;
01301 
01302             KDE_struct_stat sbuff;
01303 
01304             QByteArray file = QFile::encodeName(copy);
01305 
01306             if (KDE_stat(file.data(), &sbuff) == 0) {
01307                 if (S_ISDIR(sbuff.st_mode))
01308                     pMatch->append(QLatin1Char('/'));
01309             } else {
01310                 kDebug() << "Could not stat file" << copy;
01311             }
01312 #endif
01313         }
01314     }
01315 }
01316 
01317 void KUrlCompletion::postProcessMatches(QStringList* /*matches*/) const
01318 {
01319     // Maybe '/' should be added to directories here as in
01320     // postProcessMatch() but it would slow things down
01321     // when there are a lot of matches...
01322 }
01323 
01324 void KUrlCompletion::postProcessMatches(KCompletionMatches* /*matches*/) const
01325 {
01326     // Maybe '/' should be added to directories here as in
01327     // postProcessMatch() but it would slow things down
01328     // when there are a lot of matches...
01329 }
01330 
01331 void KUrlCompletion::customEvent(QEvent* e)
01332 {
01333     if (e->type() == CompletionMatchEvent::uniqueType()) {
01334 
01335         CompletionMatchEvent* matchEvent = static_cast<CompletionMatchEvent*>(e);
01336 
01337         matchEvent->completionThread()->wait();
01338 
01339         if (!d->isListedUrl(CTUser)) {
01340             stop();
01341             clear();
01342             d->addMatches(matchEvent->completionThread()->matches());
01343         } else {
01344             d->setListedUrl(CTUser);
01345         }
01346 
01347         if (d->userListThread == matchEvent->completionThread())
01348             d->userListThread = 0;
01349 
01350         if (d->dirListThread == matchEvent->completionThread())
01351             d->dirListThread = 0;
01352 
01353         delete matchEvent->completionThread();
01354     }
01355 }
01356 
01357 // static
01358 QString KUrlCompletion::replacedPath(const QString& text, bool replaceHome, bool replaceEnv)
01359 {
01360     if (text.isEmpty())
01361         return text;
01362 
01363     KUrlCompletionPrivate::MyURL url(text, QString());  // no need to replace something of our current cwd
01364     if (!url.kurl().isLocalFile())
01365         return text;
01366 
01367     url.filter(replaceHome, replaceEnv);
01368     return url.dir() + url.file();
01369 }
01370 
01371 
01372 QString KUrlCompletion::replacedPath(const QString& text) const
01373 {
01374     return replacedPath(text, d->replace_home, d->replace_env);
01375 }
01376 
01379 // Static functions
01380 
01381 /*
01382  * expandEnv
01383  *
01384  * Expand environment variables in text. Escaped '$' are ignored.
01385  * Return true if expansion was made.
01386  */
01387 static bool expandEnv(QString& text)
01388 {
01389     // Find all environment variables beginning with '$'
01390     //
01391     int pos = 0;
01392 
01393     bool expanded = false;
01394 
01395     while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) {
01396 
01397         // Skip escaped '$'
01398         //
01399         if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) {
01400             pos++;
01401         }
01402         // Variable found => expand
01403         //
01404         else {
01405             // Find the end of the variable = next '/' or ' '
01406             //
01407             int pos2 = text.indexOf(QLatin1Char(' '), pos + 1);
01408             int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1);
01409 
01410             if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2))
01411                 pos2 = pos_tmp;
01412 
01413             if (pos2 == -1)
01414                 pos2 = text.length();
01415 
01416             // Replace if the variable is terminated by '/' or ' '
01417             // and defined
01418             //
01419             if (pos2 >= 0) {
01420                 int len = pos2 - pos;
01421                 QString key = text.mid(pos + 1, len - 1);
01422                 QString value =
01423                     QString::fromLocal8Bit(qgetenv(key.toLocal8Bit()));
01424 
01425                 if (!value.isEmpty()) {
01426                     expanded = true;
01427                     text.replace(pos, len, value);
01428                     pos = pos + value.length();
01429                 } else {
01430                     pos = pos2;
01431                 }
01432             }
01433         }
01434     }
01435 
01436     return expanded;
01437 }
01438 
01439 /*
01440  * expandTilde
01441  *
01442  * Replace "~user" with the users home directory
01443  * Return true if expansion was made.
01444  */
01445 static bool expandTilde(QString& text)
01446 {
01447     if (text.isEmpty() || (text.at(0) != QLatin1Char('~')))
01448         return false;
01449 
01450     bool expanded = false;
01451 
01452     // Find the end of the user name = next '/' or ' '
01453     //
01454     int pos2 = text.indexOf(QLatin1Char(' '), 1);
01455     int pos_tmp = text.indexOf(QLatin1Char('/'), 1);
01456 
01457     if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2))
01458         pos2 = pos_tmp;
01459 
01460     if (pos2 == -1)
01461         pos2 = text.length();
01462 
01463     // Replace ~user if the user name is terminated by '/' or ' '
01464     //
01465     if (pos2 >= 0) {
01466 
01467         QString user = text.mid(1, pos2 - 1);
01468         QString dir;
01469 
01470         // A single ~ is replaced with $HOME
01471         //
01472         if (user.isEmpty()) {
01473             dir = QDir::homePath();
01474         }
01475         // ~user is replaced with the dir from passwd
01476         //
01477         else {
01478             struct passwd* pw = ::getpwnam(user.toLocal8Bit());
01479 
01480             if (pw)
01481                 dir = QFile::decodeName(pw->pw_dir);
01482 
01483             ::endpwent();
01484         }
01485 
01486         if (!dir.isEmpty()) {
01487             expanded = true;
01488             text.replace(0, pos2, dir);
01489         }
01490     }
01491 
01492     return expanded;
01493 }
01494 
01495 /*
01496  * unescape
01497  *
01498  * Remove escapes and return the result in a new string
01499  *
01500  */
01501 static QString unescape(const QString& text)
01502 {
01503     QString result;
01504 
01505     for (int pos = 0; pos < text.length(); pos++)
01506         if (text.at(pos) != QLatin1Char('\\'))
01507             result.insert(result.length(), text.at(pos));
01508 
01509     return result;
01510 }
01511 
01512 #include "kurlcompletion.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:35:02 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIO

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

kdelibs-4.9.5 API Reference

Skip menu "kdelibs-4.9.5 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
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
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