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

KDEUI

  • sources
  • kde-4.14
  • kdelibs
  • kdeui
  • kernel
kuniqueapplication.cpp
Go to the documentation of this file.
1 /* This file is part of the KDE libraries
2  Copyright (c) 1999 Preston Brown <pbrown@kde.org>
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License as published by the Free Software Foundation; either
7  version 2 of the License, or (at your option) any later version.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18 */
19 
20 #include "kuniqueapplication.h"
21 #include "kuniqueapplication_p.h"
22 #include <kmainwindow.h>
23 
24 #include <config.h>
25 
26 #include <sys/types.h>
27 #include <sys/wait.h>
28 
29 #include <assert.h>
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 
34 #include <QtCore/QFile>
35 #include <QtCore/QList>
36 #include <QtCore/QTimer>
37 #include <QtDBus/QtDBus>
38 
39 #include <kcmdlineargs.h>
40 #include <kstandarddirs.h>
41 #include <kaboutdata.h>
42 #include <kconfiggroup.h>
43 #include <kwindowsystem.h>
44 
45 #if defined Q_WS_X11
46 #include <kstartupinfo.h>
47 #endif
48 
49 /* I don't know why, but I end up with complaints about
50  a forward-declaration of QWidget in the activeWidow()->show
51  call below on Qt/Mac if I don't include this here... */
52 #include <QWidget>
53 
54 #include <kconfig.h>
55 #include "kdebug.h"
56 
57 #if defined Q_WS_X11
58 #include <netwm.h>
59 #include <X11/Xlib.h>
60 #define DISPLAY "DISPLAY"
61 #else
62 # ifdef Q_WS_QWS
63 # define DISPLAY "QWS_DISPLAY"
64 # else
65 # define DISPLAY "DISPLAY"
66 # endif
67 #endif
68 
69 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC)
70 #include <kkernel_mac.h>
71 #endif
72 
73 bool KUniqueApplication::Private::s_nofork = false;
74 bool KUniqueApplication::Private::s_multipleInstances = false;
75 bool s_kuniqueapplication_startCalled = false;
76 bool KUniqueApplication::Private::s_handleAutoStarted = false;
77 #ifdef Q_WS_WIN
78 /* private helpers from kapplication_win.cpp */
79 #ifndef _WIN32_WCE
80 void KApplication_activateWindowForProcess( const QString& executableName );
81 #endif
82 #endif
83 
84 void
85 KUniqueApplication::addCmdLineOptions()
86 {
87  KCmdLineOptions kunique_options;
88  kunique_options.add("nofork", ki18n("Do not run in the background."));
89 #ifdef Q_WS_MACX
90  kunique_options.add("psn", ki18n("Internally added if launched from Finder"));
91 #endif
92  KCmdLineArgs::addCmdLineOptions(kunique_options, KLocalizedString(), "kuniqueapp", "kde");
93 }
94 
95 static QDBusConnectionInterface *tryToInitDBusConnection()
96 {
97  // Check the D-Bus connection health
98  QDBusConnectionInterface* dbusService = 0;
99  QDBusConnection sessionBus = QDBusConnection::sessionBus();
100  if (!sessionBus.isConnected() || !(dbusService = sessionBus.interface()))
101  {
102  kError() << "KUniqueApplication: Cannot find the D-Bus session server: " << sessionBus.lastError().message() << endl;
103  ::exit(255);
104  }
105  return dbusService;
106 }
107 
108 bool KUniqueApplication::start()
109 {
110  return start(0);
111 }
112 
113 bool
114 KUniqueApplication::start(StartFlags flags)
115 {
116  if( s_kuniqueapplication_startCalled )
117  return true;
118  s_kuniqueapplication_startCalled = true;
119 
120  addCmdLineOptions(); // Make sure to add cmd line options
121 #if defined(Q_WS_WIN) || defined(Q_WS_MACX)
122  Private::s_nofork = true;
123 #else
124  KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kuniqueapp");
125  Private::s_nofork = !args->isSet("fork");
126 #endif
127 
128  QString appName = KCmdLineArgs::aboutData()->appName();
129  const QStringList parts = KCmdLineArgs::aboutData()->organizationDomain().split(QLatin1Char('.'), QString::SkipEmptyParts);
130  if (parts.isEmpty())
131  appName.prepend(QLatin1String("local."));
132  else
133  foreach (const QString& s, parts)
134  {
135  appName.prepend(QLatin1Char('.'));
136  appName.prepend(s);
137  }
138 
139  bool forceNewProcess = Private::s_multipleInstances || flags & NonUniqueInstance;
140 
141  if (Private::s_nofork)
142  {
143 
144 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC)
145  mac_initialize_dbus();
146 #endif
147 
148  QDBusConnectionInterface* dbusService = tryToInitDBusConnection();
149 
150  QString pid = QString::number(getpid());
151  if (forceNewProcess)
152  appName = appName + '-' + pid;
153 
154  // Check to make sure that we're actually able to register with the D-Bus session
155  // server.
156  bool registered = dbusService->registerService(appName) == QDBusConnectionInterface::ServiceRegistered;
157  if (!registered)
158  {
159  kError() << "KUniqueApplication: Can't setup D-Bus service. Probably already running."
160  << endl;
161 #if defined(Q_WS_WIN) && !defined(_WIN32_WCE)
162  KApplication_activateWindowForProcess(KCmdLineArgs::aboutData()->appName());
163 #endif
164  ::exit(255);
165  }
166 
167  // We'll call newInstance in the constructor. Do nothing here.
168  return true;
169 
170 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC)
171  } else {
172  mac_fork_and_reexec_self();
173 #endif
174 
175  }
176 
177 #ifndef Q_WS_WIN
178  int fd[2];
179  signed char result;
180  if (0 > pipe(fd))
181  {
182  kError() << "KUniqueApplication: pipe() failed!" << endl;
183  ::exit(255);
184  }
185  int fork_result = fork();
186  switch(fork_result) {
187  case -1:
188  kError() << "KUniqueApplication: fork() failed!" << endl;
189  ::exit(255);
190  break;
191  case 0:
192  {
193  // Child
194 
195  QDBusConnectionInterface* dbusService = tryToInitDBusConnection();
196  ::close(fd[0]);
197  if (forceNewProcess)
198  appName.append("-").append(QString::number(getpid()));
199 
200  QDBusReply<QDBusConnectionInterface::RegisterServiceReply> reply =
201  dbusService->registerService(appName);
202  if (!reply.isValid())
203  {
204  kError() << "KUniqueApplication: Can't setup D-Bus service." << endl;
205  result = -1;
206  ::write(fd[1], &result, 1);
207  ::exit(255);
208  }
209  if (reply == QDBusConnectionInterface::ServiceNotRegistered)
210  {
211  // Already running. Ok.
212  result = 0;
213  ::write(fd[1], &result, 1);
214  ::close(fd[1]);
215  return false;
216  }
217 
218 #ifdef Q_WS_X11
219  KStartupInfoId id;
220  if( kapp != NULL ) // KApplication constructor unsets the env. variable
221  id.initId( kapp->startupId());
222  else
223  id = KStartupInfo::currentStartupIdEnv();
224  if( !id.none())
225  { // notice about pid change
226  Display* disp = XOpenDisplay( NULL );
227  if( disp != NULL ) // use extra X connection
228  {
229  KStartupInfoData data;
230  data.addPid( getpid());
231  KStartupInfo::sendChangeX( disp, id, data );
232  XCloseDisplay( disp );
233  }
234  }
235 #else //FIXME(E): Implement
236 #endif
237  }
238  result = 0;
239  ::write(fd[1], &result, 1);
240  ::close(fd[1]);
241  return true; // Finished.
242  default:
243  // Parent
244 
245  if (forceNewProcess)
246  appName.append("-").append(QString::number(fork_result));
247  ::close(fd[1]);
248 
249  Q_FOREVER
250  {
251  int n = ::read(fd[0], &result, 1);
252  if (n == 1) break;
253  if (n == 0)
254  {
255  kError() << "KUniqueApplication: Pipe closed unexpectedly." << endl;
256  ::exit(255);
257  }
258  if (errno != EINTR)
259  {
260  kError() << "KUniqueApplication: Error reading from pipe." << endl;
261  ::exit(255);
262  }
263  }
264  ::close(fd[0]);
265 
266  if (result != 0)
267  ::exit(result); // Error occurred in child.
268 
269 #endif
270  QDBusConnectionInterface* dbusService = tryToInitDBusConnection();
271  if (!dbusService->isServiceRegistered(appName))
272  {
273  kError() << "KUniqueApplication: Registering failed!" << endl;
274  }
275 
276  QByteArray saved_args;
277  QDataStream ds(&saved_args, QIODevice::WriteOnly);
278  KCmdLineArgs::saveAppArgs(ds);
279 
280  QByteArray new_asn_id;
281 #if defined Q_WS_X11
282  KStartupInfoId id;
283  if( kapp != NULL ) // KApplication constructor unsets the env. variable
284  id.initId( kapp->startupId());
285  else
286  id = KStartupInfo::currentStartupIdEnv();
287  if( !id.none())
288  new_asn_id = id.id();
289 #endif
290 
291  QDBusMessage msg = QDBusMessage::createMethodCall(appName, "/MainApplication", "org.kde.KUniqueApplication",
292  "newInstance");
293  msg << new_asn_id << saved_args;
294  QDBusReply<int> reply = QDBusConnection::sessionBus().call(msg, QDBus::Block, INT_MAX);
295 
296  if (!reply.isValid())
297  {
298  QDBusError err = reply.error();
299  kError() << "Communication problem with " << KCmdLineArgs::aboutData()->appName() << ", it probably crashed." << endl
300  << "Error message was: " << err.name() << ": \"" << err.message() << "\"" << endl;
301  ::exit(255);
302  }
303 #ifndef Q_WS_WIN
304  ::exit(reply);
305  break;
306  }
307 #endif
308  return false; // make insure++ happy
309 }
310 
311 
312 KUniqueApplication::KUniqueApplication(bool GUIenabled, bool configUnique)
313  : KApplication( GUIenabled, Private::initHack( configUnique )),
314  d(new Private(this))
315 {
316  d->processingRequest = false;
317  d->firstInstance = true;
318 
319  // the sanity checking happened in initHack
320  new KUniqueApplicationAdaptor(this);
321 
322  if (Private::s_nofork)
323  // Can't call newInstance directly from the constructor since it's virtual...
324  QTimer::singleShot( 0, this, SLOT(_k_newInstanceNoFork()) );
325 }
326 
327 
328 #ifdef Q_WS_X11
329 KUniqueApplication::KUniqueApplication(Display *display, Qt::HANDLE visual,
330  Qt::HANDLE colormap, bool configUnique)
331  : KApplication( display, visual, colormap, Private::initHack( configUnique )),
332  d(new Private(this))
333 {
334  d->processingRequest = false;
335  d->firstInstance = true;
336 
337  // the sanity checking happened in initHack
338  new KUniqueApplicationAdaptor(this);
339 
340  if (Private::s_nofork)
341  // Can't call newInstance directly from the constructor since it's virtual...
342  QTimer::singleShot( 0, this, SLOT(_k_newInstanceNoFork()) );
343 }
344 #endif
345 
346 
347 KUniqueApplication::~KUniqueApplication()
348 {
349  delete d;
350 }
351 
352 // this gets called before even entering QApplication::QApplication()
353 KComponentData KUniqueApplication::Private::initHack(bool configUnique)
354 {
355  KComponentData cData(KCmdLineArgs::aboutData());
356  if (configUnique)
357  {
358  KConfigGroup cg(cData.config(), "KDE");
359  s_multipleInstances = cg.readEntry("MultipleInstances", false);
360  }
361  if( !KUniqueApplication::start())
362  // Already running
363  ::exit( 0 );
364  return cData;
365 }
366 
367 void KUniqueApplication::Private::_k_newInstanceNoFork()
368 {
369  s_handleAutoStarted = false;
370  q->newInstance();
371  firstInstance = false;
372 #if defined Q_WS_X11
373  // KDE4 remove
374  // A hack to make startup notification stop for apps which override newInstance()
375  // and reuse an already existing window there, but use KWindowSystem::activateWindow()
376  // instead of KStartupInfo::setNewStartupId(). Therefore KWindowSystem::activateWindow()
377  // for now sets this flag. Automatically ending startup notification always
378  // would cause problem if the new window would show up with a small delay.
379  if( s_handleAutoStarted )
380  KStartupInfo::handleAutoAppStartedSending();
381 #endif
382  // What to do with the return value ?
383 }
384 
385 bool KUniqueApplication::restoringSession()
386 {
387  return d->firstInstance && isSessionRestored();
388 }
389 
390 int KUniqueApplication::newInstance()
391 {
392  if (!d->firstInstance) {
393  QList<KMainWindow*> allWindows = KMainWindow::memberList();
394  if (!allWindows.isEmpty()) {
395  // This method is documented to only work for applications
396  // with only one mainwindow.
397  KMainWindow* mainWindow = allWindows.first();
398  if (mainWindow) {
399  mainWindow->show();
400 #ifdef Q_WS_X11
401  // This is the line that handles window activation if necessary,
402  // and what's important, it does it properly. If you reimplement newInstance(),
403  // and don't call the inherited one, use this (but NOT when newInstance()
404  // is called for the first time, like here).
405  KStartupInfo::setNewStartupId(mainWindow, startupId());
406 #endif
407 #ifdef Q_WS_WIN
408  KWindowSystem::forceActiveWindow( mainWindow->winId() );
409 #endif
410 
411  }
412  }
413  }
414  return 0; // do nothing in default implementation
415 }
416 
417 #ifndef KDE_NO_DEPRECATED
418 void KUniqueApplication::setHandleAutoStarted()
419 {
420  Private::s_handleAutoStarted = false;
421 }
422 #endif
423 
425 
426 int KUniqueApplicationAdaptor::newInstance(const QByteArray &asn_id, const QByteArray &args)
427 {
428  if (!asn_id.isEmpty())
429  parent()->setStartupId(asn_id);
430 
431  const int index = parent()->metaObject()->indexOfMethod("loadCommandLineOptionsForNewInstance");
432  if (index != -1) {
433  // This hook allows the application to set up KCmdLineArgs using addCmdLineOptions
434  // before we load the app args. Normally not necessary, but needed by kontact
435  // since it switches to other sets of options when called as e.g. kmail or korganizer
436  QMetaObject::invokeMethod(parent(), "loadCommandLineOptionsForNewInstance");
437  }
438 
439  QDataStream ds(args);
440  KCmdLineArgs::loadAppArgs(ds);
441 
442  int ret = parent()->newInstance();
443  // Must be done out of the newInstance code, in case it is overloaded
444  parent()->d->firstInstance = false;
445  return ret;
446 }
447 
448 #include "kuniqueapplication.moc"
449 #include "kuniqueapplication_p.moc"
kapp
#define kapp
Definition: kapplication.h:56
KApplication
Controls and provides information to all KDE applications.
Definition: kapplication.h:82
KStartupInfo::setNewStartupId
static void setNewStartupId(QWidget *window, const QByteArray &startup_id)
Use this function if the application got a request with startup notification from outside (for exampl...
Definition: kstartupinfo.cpp:643
KCmdLineArgs::addCmdLineOptions
static void addCmdLineOptions(const KCmdLineOptions &options, const KLocalizedString &name=KLocalizedString(), const QByteArray &id=QByteArray(), const QByteArray &afterId=QByteArray())
KUniqueApplication::start
static bool start()
Definition: kuniqueapplication.cpp:108
QString::append
QString & append(QChar ch)
KAboutData::organizationDomain
QString organizationDomain() const
kdebug.h
KAboutData::appName
QString appName() const
KCmdLineOptions::add
KCmdLineOptions & add(const QByteArray &name, const KLocalizedString &description=KLocalizedString(), const QByteArray &defaultValue=QByteArray())
QByteArray
QDBusConnection::call
QDBusMessage call(const QDBusMessage &message, QDBus::CallMode mode, int timeout) const
KCmdLineArgs::aboutData
static const KAboutData * aboutData()
KStartupInfo::handleAutoAppStartedSending
static void handleAutoAppStartedSending()
Definition: kstartupinfo.cpp:637
QDBusReply
kkernel_mac.h
ki18n
KLocalizedString ki18n(const char *msg)
QDataStream
QString::split
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
KCmdLineArgs::parsedArgs
static KCmdLineArgs * parsedArgs(const QByteArray &id=QByteArray())
QString::prepend
QString & prepend(QChar ch)
QDBusConnection::interface
QDBusConnectionInterface * interface() const
kconfig.h
KCmdLineArgs
QDBusError::name
QString name() const
QByteArray::isEmpty
bool isEmpty() const
QDBusError::message
QString message() const
kError
static QDebug kError(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
QDBusConnection
QDBusReply::isValid
bool isValid() const
KStartupInfoId
Class representing an identification of application startup notification.
Definition: kstartupinfo.h:368
QDBusConnection::sessionBus
QDBusConnection sessionBus()
kstartupinfo.h
KUniqueApplication::setHandleAutoStarted
static void setHandleAutoStarted()
Definition: kuniqueapplication.cpp:418
KUniqueApplication::KUniqueApplicationAdaptor
friend class KUniqueApplicationAdaptor
Definition: kuniqueapplication.h:223
KWindowSystem::forceActiveWindow
static void forceActiveWindow(WId win, long time=0)
Sets window win to be the active window.
Definition: kwindowsystem_mac.cpp:366
QDBusConnectionInterface::isServiceRegistered
QDBusReply< bool > isServiceRegistered(const QString &serviceName) const
KUniqueApplication::~KUniqueApplication
virtual ~KUniqueApplication()
Destructor.
Definition: kuniqueapplication.cpp:347
mac_fork_and_reexec_self
void mac_fork_and_reexec_self()
KApplication_activateWindowForProcess
void KApplication_activateWindowForProcess(const QString &executableName)
Definition: kapplication_win.cpp:242
KCmdLineArgs::isSet
bool isSet(const QByteArray &option) const
KCmdLineArgs::loadAppArgs
static void loadAppArgs(QDataStream &)
QCoreApplication::exit
void exit(int returnCode)
QString::number
QString number(int n, int base)
netwm.h
KUniqueApplication::restoringSession
bool restoringSession()
Returns whether newInstance() is being called while session restoration is in progress.
Definition: kuniqueapplication.cpp:385
tryToInitDBusConnection
static QDBusConnectionInterface * tryToInitDBusConnection()
Definition: kuniqueapplication.cpp:95
KStartupInfoData
Class representing data about an application startup notification.
Definition: kstartupinfo.h:439
KMainWindow
KDE top level main window
Definition: kmainwindow.h:106
KApplication::startupId
QByteArray startupId() const
Returns the app startup notification identifier for this running application.
Definition: kapplication.cpp:1075
QDBusConnectionInterface
QList::isEmpty
bool isEmpty() const
kcmdlineargs.h
kuniqueapplication.h
KUniqueApplication::newInstance
virtual int newInstance()
Creates a new "instance" of the application.
Definition: kuniqueapplication.cpp:390
QWidget::winId
WId winId() const
QList::first
T & first()
Qt::HANDLE
typedef HANDLE
QString
QList
QStringList
QLatin1Char
QMetaObject::invokeMethod
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
kmainwindow.h
KConfigGroup
KUniqueApplication::addCmdLineOptions
static void addCmdLineOptions()
Adds command line options specific for KUniqueApplication.
Definition: kuniqueapplication.cpp:85
KCmdLineArgs::saveAppArgs
static void saveAppArgs(QDataStream &)
KStartupInfo::currentStartupIdEnv
static KStartupInfoId currentStartupIdEnv()
Returns the current startup notification identification for the current startup notification environm...
Definition: kstartupinfo.cpp:1090
QDBusConnection::isConnected
bool isConnected() const
QDBusMessage
QApplication::isSessionRestored
bool isSessionRestored() const
QLatin1String
KUniqueApplication::NonUniqueInstance
Create a new instance of the application in a new process and do not attempt to re-use an existing pr...
Definition: kuniqueapplication.h:119
s_kuniqueapplication_startCalled
bool s_kuniqueapplication_startCalled
Definition: kuniqueapplication.cpp:75
KStartupInfo::sendChangeX
static bool sendChangeX(Display *dpy, const KStartupInfoId &id, const KStartupInfoData &data)
Like sendChange , uses dpy instead of qt_x11display() for sending the info.
Definition: kstartupinfo.cpp:510
kstandarddirs.h
kwindowsystem.h
KUniqueApplication::KUniqueApplication
KUniqueApplication(bool GUIenabled=true, bool configUnique=false)
Constructor.
Definition: kuniqueapplication.cpp:312
QDBusConnectionInterface::registerService
QDBusReply< QDBusConnectionInterface::RegisterServiceReply > registerService(const QString &serviceName, ServiceQueueOptions qoption, ServiceReplacementOptions roption)
QWidget::show
void show()
kaboutdata.h
QDBusConnection::lastError
QDBusError lastError() const
mac_initialize_dbus
void mac_initialize_dbus()
QDBusReply::error
const QDBusError & error()
KStartupInfoId::initId
void initId(const QByteArray &id="")
Initializes this object with the given identification ( which may be also "0" for no notification )...
Definition: kstartupinfo.cpp:1058
KCmdLineOptions
KConfigGroup::readEntry
T readEntry(const QString &key, const T &aDefault) const
KStartupInfoData::addPid
void addPid(pid_t pid)
Adds a PID to the list of processes that belong to the startup notification.
Definition: kstartupinfo.cpp:1446
KLocalizedString
QDBusMessage::createMethodCall
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
KComponentData
KStandardAction::close
KAction * close(const QObject *recvr, const char *slot, QObject *parent)
Close the current document.
Definition: kstandardaction.cpp:269
KMainWindow::memberList
static QList< KMainWindow * > memberList()
List of members of KMainWindow class.
Definition: kmainwindow.cpp:1218
kconfiggroup.h
QDBusError
QTimer::singleShot
singleShot
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:24:00 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KDEUI

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

kdelibs API Reference

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

Search



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

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