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

libkleo

  • sources
  • kde-4.14
  • kdepim
  • libkleo
  • kleo
checksumdefinition.cpp
Go to the documentation of this file.
1 /* -*- mode: c++; c-basic-offset:4 -*-
2  checksumdefinition.cpp
3 
4  This file is part of libkleopatra, the KDE keymanagement library
5  Copyright (c) 2010 Klarälvdalens Datakonsult AB
6 
7  Libkleopatra is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of the
10  License, or (at your option) any later version.
11 
12  Libkleopatra is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License along
18  with this program; if not, write to the Free Software Foundation, Inc.,
19  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 
21  In addition, as a special exception, the copyright holders give
22  permission to link the code of this program with any edition of
23  the Qt library by Trolltech AS, Norway (or with modified versions
24  of Qt that use the same license as Qt), and distribute linked
25  combinations including the two. You must obey the GNU General
26  Public License in all respects for all of the code used other than
27  Qt. If you modify this file, you may extend this exception to
28  your version of the file, but you are not obligated to do so. If
29  you do not wish to do so, delete this exception statement from
30  your version.
31 */
32 
33 #include "checksumdefinition.h"
34 
35 #include "exception.h"
36 #include "cryptobackendfactory.h"
37 
38 #include <KConfigGroup>
39 #include <KLocalizedString>
40 #include <KGlobal>
41 #include <KConfig>
42 #include <KShell>
43 #include <KStandardDirs>
44 
45 #include <QString>
46 #include <QStringList>
47 #include <QDebug>
48 #include <QFileInfo>
49 #include <QProcess>
50 #include <QByteArray>
51 #include <QMutex>
52 #include <QCoreApplication>
53 
54 #ifndef Q_MOC_RUN
55 #include <boost/shared_ptr.hpp>
56 #endif
57 
58 #ifdef stdin
59 # undef stdin // pah..
60 #endif
61 
62 using namespace Kleo;
63 using namespace boost;
64 
65 static QMutex installPathMutex;
66 Q_GLOBAL_STATIC( QString, _installPath )
67 QString ChecksumDefinition::installPath() {
68  const QMutexLocker locker( &installPathMutex );
69  QString * const ip = _installPath();
70  if ( ip->isEmpty() ) {
71  if ( QCoreApplication::instance() ) {
72  *ip = QCoreApplication::applicationDirPath();
73  } else {
74  qWarning( "checksumdefinition.cpp: installPath() called before QCoreApplication was constructed" );
75  }
76  }
77  return *ip;
78 }
79 void ChecksumDefinition::setInstallPath( const QString & ip ) {
80  const QMutexLocker locker( &installPathMutex );
81  *_installPath() =ip;
82 }
83 
84 
85 // Checksum Definition #N groups
86 static const QLatin1String ID_ENTRY( "id" );
87 static const QLatin1String NAME_ENTRY( "Name" );
88 static const QLatin1String CREATE_COMMAND_ENTRY( "create-command" );
89 static const QLatin1String VERIFY_COMMAND_ENTRY( "verify-command" );
90 static const QLatin1String FILE_PATTERNS_ENTRY( "file-patterns" );
91 static const QLatin1String OUTPUT_FILE_ENTRY( "output-file" );
92 static const QLatin1String FILE_PLACEHOLDER( "%f" );
93 static const QLatin1String INSTALLPATH_PLACEHOLDER( "%I" );
94 static const QLatin1String NULL_SEPARATED_STDIN_INDICATOR( "0|" );
95 static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR( '|' );
96 
97 // ChecksumOperations group
98 static const QLatin1String CHECKSUM_DEFINITION_ID_ENTRY( "checksum-definition-id" );
99 
100 
101 namespace {
102 
103  class ChecksumDefinitionError : public Kleo::Exception {
104  const QString m_id;
105  public:
106  ChecksumDefinitionError( const QString & id, const QString & message )
107  : Kleo::Exception( GPG_ERR_INV_PARAMETER, i18n("Error in checksum definition %1: %2", id, message ), MessageOnly ),
108  m_id( id )
109  {
110 
111  }
112  ~ChecksumDefinitionError() throw() {}
113 
114  const QString & checksumDefinitionId() const { return m_id; }
115  };
116 
117 }
118 
119 static QString try_extensions( const QString & path ) {
120  static const char exts[][4] = {
121  "", "exe", "bat", "bin", "cmd",
122  };
123  static const size_t numExts = sizeof exts / sizeof *exts ;
124  for ( unsigned int i = 0 ; i < numExts ; ++i ) {
125  const QFileInfo fi( path + QLatin1Char('.') + QLatin1String( exts[i] ) );
126  if ( fi.exists() )
127  return fi.filePath();
128  }
129  return QString();
130 }
131 
132 static void parse_command( QString cmdline, const QString & id, const QString & whichCommand,
133  QString * command, QStringList * prefix, QStringList * suffix, ChecksumDefinition::ArgumentPassingMethod * method )
134 {
135  assert( prefix );
136  assert( suffix );
137  assert( method );
138 
139  KShell::Errors errors;
140  QStringList l;
141 
142  if ( cmdline.startsWith( NULL_SEPARATED_STDIN_INDICATOR ) ) {
143  *method = ChecksumDefinition::NullSeparatedInputFile;
144  cmdline.remove( 0, 2 );
145  } else if ( cmdline.startsWith( NEWLINE_SEPARATED_STDIN_INDICATOR ) ) {
146  *method = ChecksumDefinition::NewlineSeparatedInputFile;
147  cmdline.remove( 0, 1 );
148  } else {
149  *method = ChecksumDefinition::CommandLine;
150  }
151  if ( *method != ChecksumDefinition::CommandLine && cmdline.contains( FILE_PLACEHOLDER ) )
152  throw ChecksumDefinitionError( id, i18n("Cannot use both %f and | in '%1'", whichCommand) );
153  cmdline.replace( FILE_PLACEHOLDER, QLatin1String("__files_go_here__") )
154  .replace( INSTALLPATH_PLACEHOLDER, QLatin1String("__path_goes_here__") );
155  l = KShell::splitArgs( cmdline, KShell::AbortOnMeta|KShell::TildeExpand, &errors );
156  l = l.replaceInStrings( QLatin1String("__files_go_here__"), FILE_PLACEHOLDER );
157  if ( l.indexOf( QRegExp( QLatin1String(".*__path_goes_here__.*") ) ) >= 0 )
158  l = l.replaceInStrings( QLatin1String("__path_goes_here__"), ChecksumDefinition::installPath() );
159  if ( errors == KShell::BadQuoting )
160  throw ChecksumDefinitionError( id, i18n("Quoting error in '%1' entry", whichCommand) );
161  if ( errors == KShell::FoundMeta )
162  throw ChecksumDefinitionError( id, i18n("'%1' too complex (would need shell)", whichCommand) );
163  qDebug() << "ChecksumDefinition[" << id << ']' << l;
164  if ( l.empty() )
165  throw ChecksumDefinitionError( id, i18n("'%1' entry is empty/missing", whichCommand) );
166  const QFileInfo fi1( l.front() );
167  if ( fi1.isAbsolute() )
168  *command = try_extensions( l.front() );
169  else
170  *command = KStandardDirs::findExe( fi1.fileName() );
171  if ( command->isEmpty() )
172  throw ChecksumDefinitionError( id, i18n("'%1' empty or not found", whichCommand) );
173  const int idx1 = l.indexOf( FILE_PLACEHOLDER );
174  if ( idx1 < 0 ) {
175  // none -> append
176  *prefix = l.mid( 1 );
177  } else {
178  *prefix = l.mid( 1, idx1-1 );
179  *suffix = l.mid( idx1+1 );
180  }
181  switch ( *method ) {
182  case ChecksumDefinition::CommandLine:
183  qDebug() << "ChecksumDefinition[" << id << ']' << *command << *prefix << FILE_PLACEHOLDER << *suffix;
184  break;
185  case ChecksumDefinition::NewlineSeparatedInputFile:
186  qDebug() << "ChecksumDefinition[" << id << ']' << "find | " << *command << *prefix;
187  break;
188  case ChecksumDefinition::NullSeparatedInputFile:
189  qDebug() << "ChecksumDefinition[" << id << ']' << "find -print0 | " << *command << *prefix;
190  break;
191  case ChecksumDefinition::NumArgumentPassingMethods:
192  assert( !"Should not happen" );
193  break;
194  }
195 }
196 
197 namespace {
198 
199  class KConfigBasedChecksumDefinition : public ChecksumDefinition {
200  public:
201  explicit KConfigBasedChecksumDefinition( const KConfigGroup & group )
202  : ChecksumDefinition( group.readEntryUntranslated( ID_ENTRY ),
203  group.readEntry( NAME_ENTRY ),
204  group.readEntry( OUTPUT_FILE_ENTRY ),
205  group.readEntry( FILE_PATTERNS_ENTRY, QStringList() ) )
206  {
207  if ( id().isEmpty() )
208  throw ChecksumDefinitionError( group.name(), i18n("'id' entry is empty/missing") );
209  if ( outputFileName().isEmpty() )
210  throw ChecksumDefinitionError( id(), i18n("'output-file' entry is empty/missing") );
211  if ( patterns().empty() )
212  throw ChecksumDefinitionError( id(), i18n("'file-patterns' entry is empty/missing") );
213 
214  // create-command
215  ArgumentPassingMethod method;
216  parse_command( group.readEntry( CREATE_COMMAND_ENTRY ), id(), CREATE_COMMAND_ENTRY,
217  &m_createCommand, &m_createPrefixArguments, &m_createPostfixArguments, &method );
218  setCreateCommandArgumentPassingMethod( method );
219 
220  // verify-command
221  parse_command( group.readEntry( VERIFY_COMMAND_ENTRY ), id(), VERIFY_COMMAND_ENTRY,
222  &m_verifyCommand, &m_verifyPrefixArguments, &m_verifyPostfixArguments, &method );
223  setVerifyCommandArgumentPassingMethod( method );
224  }
225 
226  private:
227  /* reimp */ QString doGetCreateCommand() const { return m_createCommand; }
228  /* reimp */ QStringList doGetCreateArguments( const QStringList & files ) const {
229  return m_createPrefixArguments + files + m_createPostfixArguments;
230  }
231  /* reimp */ QString doGetVerifyCommand() const { return m_verifyCommand; }
232  /* reimp */ QStringList doGetVerifyArguments( const QStringList & files ) const {
233  return m_verifyPrefixArguments + files + m_verifyPostfixArguments;
234  }
235 
236  private:
237  QString m_createCommand, m_verifyCommand;
238  QStringList m_createPrefixArguments, m_createPostfixArguments;
239  QStringList m_verifyPrefixArguments, m_verifyPostfixArguments;
240  };
241 
242 }
243 
244 ChecksumDefinition::ChecksumDefinition( const QString & id, const QString & label, const QString & outputFileName, const QStringList & patterns )
245  : m_id( id ),
246  m_label( label.isEmpty() ? id : label ),
247  m_outputFileName( outputFileName ),
248  m_patterns( patterns ),
249  m_createMethod( CommandLine ),
250  m_verifyMethod( CommandLine )
251 {
252 
253 }
254 
255 ChecksumDefinition::~ChecksumDefinition() {}
256 
257 QString ChecksumDefinition::createCommand() const {
258  return doGetCreateCommand();
259 }
260 
261 QString ChecksumDefinition::verifyCommand() const {
262  return doGetVerifyCommand();
263 }
264 
265 #if 0
266 QStringList ChecksumDefinition::createCommandArguments( const QStringList & files ) const {
267  return doGetCreateArguments( files );
268 }
269 
270 QStringList ChecksumDefinition::verifyCommandArguments( const QStringList & files ) const {
271  return doGetVerifyArguments( files );
272 }
273 #endif
274 
275 static QByteArray make_input( const QStringList & files, char sep ) {
276  QByteArray result;
277  Q_FOREACH( const QString & file, files ) {
278  result += QFile::encodeName( file );
279  result += sep;
280  }
281  return result;
282 }
283 
284 static bool start_command( QProcess * p, const char * functionName,
285  const QString & cmd, const QStringList & args,
286  const QStringList & files, ChecksumDefinition::ArgumentPassingMethod method )
287 {
288  if ( !p ) {
289  qWarning( "%s: process == NULL", functionName );
290  return false;
291  }
292 
293  switch ( method ) {
294 
295  case ChecksumDefinition::NumArgumentPassingMethods:
296  assert( !"Should not happen" );
297 
298  case ChecksumDefinition::CommandLine:
299  qDebug( "[%p] Starting %s %s", p, qPrintable( cmd ), qPrintable( args.join(QLatin1String(" ")) ) );
300  p->start( cmd, args, QIODevice::ReadOnly );
301  return true;
302 
303  case ChecksumDefinition::NewlineSeparatedInputFile:
304  case ChecksumDefinition::NullSeparatedInputFile:
305  p->start( cmd, args, QIODevice::ReadWrite );
306  if ( !p->waitForStarted() )
307  return false;
308  const char sep =
309  method == ChecksumDefinition::NewlineSeparatedInputFile ? '\n' :
310  /* else */ '\0' ;
311  const QByteArray stdin = make_input( files, sep );
312  if ( p->write( stdin ) != stdin.size() )
313  return false;
314  p->closeWriteChannel();
315  return true;
316  }
317 
318  return false; // make compiler happy
319 
320 }
321 
322 bool ChecksumDefinition::startCreateCommand( QProcess * p, const QStringList & files ) const {
323  return start_command( p, Q_FUNC_INFO,
324  doGetCreateCommand(),
325  m_createMethod == CommandLine ? doGetCreateArguments( files ) :
326  /* else */ doGetCreateArguments( QStringList() ),
327  files, m_createMethod );
328 }
329 
330 bool ChecksumDefinition::startVerifyCommand( QProcess * p, const QStringList & files ) const {
331  return start_command( p, Q_FUNC_INFO,
332  doGetVerifyCommand(),
333  m_verifyMethod == CommandLine ? doGetVerifyArguments( files ) :
334  /* else */ doGetVerifyArguments( QStringList() ),
335  files, m_verifyMethod );
336 }
337 
338 // static
339 std::vector< shared_ptr<ChecksumDefinition> > ChecksumDefinition::getChecksumDefinitions() {
340  QStringList errors;
341  return getChecksumDefinitions( errors );
342 }
343 
344 // static
345 std::vector< shared_ptr<ChecksumDefinition> > ChecksumDefinition::getChecksumDefinitions( QStringList & errors ) {
346  std::vector< shared_ptr<ChecksumDefinition> > result;
347  if ( KConfig * config = CryptoBackendFactory::instance()->configObject() ) {
348  const QStringList groups = config->groupList().filter( QRegExp(QLatin1String("^Checksum Definition #")) );
349  result.reserve( groups.size() );
350  Q_FOREACH( const QString & group, groups )
351  try {
352  const shared_ptr<ChecksumDefinition> ad( new KConfigBasedChecksumDefinition( KConfigGroup( config, group ) ) );
353  result.push_back( ad );
354  } catch ( const std::exception & e ) {
355  qDebug() << e.what();
356  errors.push_back( QString::fromLocal8Bit( e.what() ) );
357  } catch ( ... ) {
358  errors.push_back( i18n("Caught unknown exception in group %1", group ) );
359  }
360  }
361  return result;
362 }
363 
364 // static
365 shared_ptr<ChecksumDefinition> ChecksumDefinition::getDefaultChecksumDefinition( const std::vector< shared_ptr<ChecksumDefinition> > & checksumDefinitions ) {
366  const KConfigGroup group( KGlobal::config(), "ChecksumOperations" );
367  const QString checksumDefinitionId = group.readEntry( CHECKSUM_DEFINITION_ID_ENTRY );
368  if ( !checksumDefinitionId.isEmpty() )
369  Q_FOREACH( const shared_ptr<ChecksumDefinition> & cd, checksumDefinitions )
370  if ( cd && cd->id() == checksumDefinitionId )
371  return cd;
372  if ( !checksumDefinitions.empty() )
373  return checksumDefinitions.front();
374  else
375  return shared_ptr<ChecksumDefinition>();
376 }
377 
378 // static
379 void ChecksumDefinition::setDefaultChecksumDefinition( const shared_ptr<ChecksumDefinition> & checksumDefinition ) {
380  if ( !checksumDefinition )
381  return;
382  KConfigGroup group( KGlobal::config(), "ChecksumOperations" );
383  group.writeEntry( CHECKSUM_DEFINITION_ID_ENTRY, checksumDefinition->id() );
384  group.sync();
385 }
386 
Kleo::ChecksumDefinition::startCreateCommand
bool startCreateCommand(QProcess *process, const QStringList &files) const
Definition: checksumdefinition.cpp:322
parse_command
static void parse_command(QString cmdline, const QString &id, const QString &whichCommand, QString *command, QStringList *prefix, QStringList *suffix, ChecksumDefinition::ArgumentPassingMethod *method)
Definition: checksumdefinition.cpp:132
QString::indexOf
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
QMutex
Kleo::ChecksumDefinition::ArgumentPassingMethod
ArgumentPassingMethod
Definition: checksumdefinition.h:57
QList::push_back
void push_back(const T &value)
QByteArray
QList::reserve
void reserve(int alloc)
Kleo::ChecksumDefinition::verifyCommand
QString verifyCommand() const
Definition: checksumdefinition.cpp:261
start_command
static bool start_command(QProcess *p, const char *functionName, const QString &cmd, const QStringList &args, const QStringList &files, ChecksumDefinition::ArgumentPassingMethod method)
Definition: checksumdefinition.cpp:284
Kleo::ChecksumDefinition::getChecksumDefinitions
static std::vector< boost::shared_ptr< ChecksumDefinition > > getChecksumDefinitions()
Definition: checksumdefinition.cpp:339
QStringList::join
QString join(const QString &separator) const
QString::remove
QString & remove(int position, int n)
installPathMutex
static QMutex installPathMutex
Definition: checksumdefinition.cpp:65
Kleo::CryptoBackendFactory::instance
static CryptoBackendFactory * instance()
Definition: cryptobackendfactory.cpp:102
CREATE_COMMAND_ENTRY
static const QLatin1String CREATE_COMMAND_ENTRY("create-command")
CHECKSUM_DEFINITION_ID_ENTRY
static const QLatin1String CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id")
QList::size
int size() const
QFileInfo::filePath
QString filePath() const
exception.h
Kleo::ChecksumDefinition
Definition: checksumdefinition.h:51
QRegExp
boost::shared_ptr
Definition: checksumdefinition.h:46
Kleo::ChecksumDefinition::NullSeparatedInputFile
Definition: checksumdefinition.h:60
QString::fromLocal8Bit
QString fromLocal8Bit(const char *str, int size)
label
const char * label
Definition: cryptoconfigmodule.cpp:516
Kleo::ChecksumDefinition::createCommand
QString createCommand() const
Definition: checksumdefinition.cpp:257
QProcess
Kleo::ChecksumDefinition::NewlineSeparatedInputFile
Definition: checksumdefinition.h:59
QString::isEmpty
bool isEmpty() const
cryptobackendfactory.h
Kleo::ChecksumDefinition::ChecksumDefinition
ChecksumDefinition(const QString &id, const QString &label, const QString &outputFileName, const QStringList &extensions)
Definition: checksumdefinition.cpp:244
QString::startsWith
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
Kleo::ChecksumDefinition::setInstallPath
static void setInstallPath(const QString &ip)
Definition: checksumdefinition.cpp:79
Kleo::ChecksumDefinition::startVerifyCommand
bool startVerifyCommand(QProcess *process, const QStringList &files) const
Definition: checksumdefinition.cpp:330
QCoreApplication::instance
QCoreApplication * instance()
QStringList::replaceInStrings
QStringList & replaceInStrings(const QString &before, const QString &after, Qt::CaseSensitivity cs)
Kleo::ChecksumDefinition::setDefaultChecksumDefinition
static void setDefaultChecksumDefinition(const boost::shared_ptr< ChecksumDefinition > &checksumDefinition)
Definition: checksumdefinition.cpp:379
Kleo::ChecksumDefinition::CommandLine
Definition: checksumdefinition.h:58
FILE_PATTERNS_ENTRY
static const QLatin1String FILE_PATTERNS_ENTRY("file-patterns")
QString
NULL_SEPARATED_STDIN_INDICATOR
static const QLatin1String NULL_SEPARATED_STDIN_INDICATOR("0|")
QStringList
make_input
static QByteArray make_input(const QStringList &files, char sep)
Definition: checksumdefinition.cpp:275
QFileInfo
QFileInfo::exists
bool exists() const
QString::contains
bool contains(QChar ch, Qt::CaseSensitivity cs) const
QProcess::waitForStarted
bool waitForStarted(int msecs)
QLatin1Char
Kleo::Exception
Definition: exception.h:45
QString::replace
QString & replace(int position, int n, QChar after)
Kleo::ChecksumDefinition::~ChecksumDefinition
virtual ~ChecksumDefinition()
Definition: checksumdefinition.cpp:255
Kleo::ChecksumDefinition::installPath
static QString installPath()
Definition: checksumdefinition.cpp:67
Kleo::ChecksumDefinition::getDefaultChecksumDefinition
static boost::shared_ptr< ChecksumDefinition > getDefaultChecksumDefinition(const std::vector< boost::shared_ptr< ChecksumDefinition > > &available)
Definition: checksumdefinition.cpp:365
try_extensions
static QString try_extensions(const QString &path)
Definition: checksumdefinition.cpp:119
Kleo::ChecksumDefinition::NumArgumentPassingMethods
Definition: checksumdefinition.h:62
OUTPUT_FILE_ENTRY
static const QLatin1String OUTPUT_FILE_ENTRY("output-file")
QLatin1String
ID_ENTRY
static const QLatin1String ID_ENTRY("id")
QMutexLocker
QList::mid
QList< T > mid(int pos, int length) const
FILE_PLACEHOLDER
static const QLatin1String FILE_PLACEHOLDER("%f")
QIODevice::write
qint64 write(const char *data, qint64 maxSize)
QStringList::indexOf
int indexOf(const QRegExp &rx, int from) const
INSTALLPATH_PLACEHOLDER
static const QLatin1String INSTALLPATH_PLACEHOLDER("%I")
VERIFY_COMMAND_ENTRY
static const QLatin1String VERIFY_COMMAND_ENTRY("verify-command")
QStringList::filter
QStringList filter(const QString &str, Qt::CaseSensitivity cs) const
QByteArray::size
int size() const
QCoreApplication::applicationDirPath
QString applicationDirPath()
QProcess::closeWriteChannel
void closeWriteChannel()
NAME_ENTRY
static const QLatin1String NAME_ENTRY("Name")
checksumdefinition.h
QProcess::start
void start(const QString &program, const QStringList &arguments, QFlags< QIODevice::OpenModeFlag > mode)
QFile::encodeName
QByteArray encodeName(const QString &fileName)
NEWLINE_SEPARATED_STDIN_INDICATOR
static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR( '|')
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:33:38 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

libkleo

Skip menu "libkleo"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members

kdepim API Reference

Skip menu "kdepim API Reference"
  • akonadi_next
  • akregator
  • blogilo
  • calendarsupport
  • console
  •   kabcclient
  •   konsolekalendar
  • kaddressbook
  • kalarm
  •   lib
  • kdgantt2
  • kjots
  • kleopatra
  • kmail
  • knode
  • knotes
  • kontact
  • korgac
  • korganizer
  • ktimetracker
  • libkdepim
  • libkleo
  • libkpgp
  • mailcommon
  • messagelist
  • messageviewer
  • pimprint

Search



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

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