Commit 75d3c02a authored by Stefano Crocco's avatar Stefano Crocco
Browse files

Fix session management with preloaded windows

Session management stopped working corectly with preloaded windows when
Konqueror was ported from having many application instances to a single
application with many windows. The session management code wasn't
changed accordingly, which caused preloaded windows to be restored as
full windows.

To correctly exclude preloaded windows from session management, we use
KMainWindow::saveGlobalProperties to store the indexes of preloaded
windows and KMainWindow::readGlobalProperties to read them back, so that
we know which windows should not be restored.

This commit also refactors much of the startup code, moving it from
main() to KonqApplication.

BUG: 450624


(cherry picked from commit 26d98061)
parent 28760cd6
Pipeline #264862 canceled with stage
......@@ -8,12 +8,37 @@
#include "konqapplication.h"
#include "konqsettings.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <konqueror-version.h>
#include "konqmainwindow.h"
#include "KonquerorAdaptor.h"
#include "konqviewmanager.h"
#include "konqurl.h"
#include "konqsettingsxt.h"
#include "konqsessionmanager.h"
#include "konqclosedwindowsmanager.h"
#include "konqdebug.h"
#include "konqmainwindow.h"
#include "konqmainwindowfactory.h"
#include "konqmisc.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QDir>
#include <QProcess>
#include <QDirIterator>
#include <QTextStream>
#include <KCrash>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KDBusService>
#include <KStartupInfo>
#include <KWindowSystem>
#include <kwindowsystem_version.h>
#include <iostream>
KonquerorApplication::KonquerorApplication(int &argc, char **argv)
: QApplication(argc, argv)
......@@ -40,6 +65,8 @@ KonquerorApplication::KonquerorApplication(int &argc, char **argv)
QByteArray flags = qgetenv("QTWEBENGINE_CHROMIUM_FLAGS");
flags.append(" --enable-features=WebRTCPipeWireCapturer");
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", flags);
KLocalizedString::setApplicationDomain("konqueror");
}
void KonquerorApplication::slotReparseConfiguration()
......@@ -70,3 +97,307 @@ void KonquerorApplication::slotComboCleared(const QDBusMessage &msg)
KonqMainWindow::comboAction(KonqMainWindow::ComboClear, QString(), msg.service());
}
void KonquerorApplication::setupAboutData()
{
KAboutData m_aboutData("konqueror", i18n("Konqueror"), KONQUEROR_VERSION);
m_aboutData.setShortDescription(i18n("Web browser, file manager and document viewer."));
m_aboutData.addLicense(KAboutLicense::GPL_V2);
m_aboutData.setCopyrightStatement(i18n("(C) 1999-2016, The Konqueror developers"));
m_aboutData.setHomepage("https://konqueror.org/");
m_aboutData.addAuthor(i18n("Stefano Crocco"), i18n("Current maintainer"), "stefano.crocco@alice.it");
m_aboutData.addAuthor(i18n("David Faure"), i18n("Developer (framework, parts, JavaScript, I/O library) and former maintainer"), "faure@kde.org");
m_aboutData.addAuthor(i18n("Simon Hausmann"), i18n("Developer (framework, parts)"), "hausmann@kde.org");
m_aboutData.addAuthor(i18n("Michael Reiher"), i18n("Developer (framework)"), "michael.reiher@gmx.de");
m_aboutData.addAuthor(i18n("Matthias Welk"), i18n("Developer"), "welk@fokus.gmd.de");
m_aboutData.addAuthor(i18n("Alexander Neundorf"), i18n("Developer (List views)"), "neundorf@kde.org");
m_aboutData.addAuthor(i18n("Michael Brade"), i18n("Developer (List views, I/O library)"), "brade@kde.org");
m_aboutData.addAuthor(i18n("Lars Knoll"), i18n("Developer (HTML rendering engine)"), "knoll@kde.org");
m_aboutData.addAuthor(i18n("Dirk Mueller"), i18n("Developer (HTML rendering engine)"), "mueller@kde.org");
m_aboutData.addAuthor(i18n("Peter Kelly"), i18n("Developer (HTML rendering engine)"), "pmk@post.com");
m_aboutData.addAuthor(i18n("Waldo Bastian"), i18n("Developer (HTML rendering engine, I/O library)"), "bastian@kde.org");
m_aboutData.addAuthor(i18n("Germain Garand"), i18n("Developer (HTML rendering engine)"), "germain@ebooksfrance.org");
m_aboutData.addAuthor(i18n("Leo Savernik"), i18n("Developer (HTML rendering engine)"), "l.savernik@aon.at");
m_aboutData.addAuthor(i18n("Stephan Kulow"), i18n("Developer (HTML rendering engine, I/O library, regression test framework)"), "coolo@kde.org");
m_aboutData.addAuthor(i18n("Antti Koivisto"), i18n("Developer (HTML rendering engine)"), "koivisto@kde.org");
m_aboutData.addAuthor(i18n("Zack Rusin"), i18n("Developer (HTML rendering engine)"), "zack@kde.org");
m_aboutData.addAuthor(i18n("Tobias Anton"), i18n("Developer (HTML rendering engine)"), "anton@stud.fbi.fh-darmstadt.de");
m_aboutData.addAuthor(i18n("Lubos Lunak"), i18n("Developer (HTML rendering engine)"), "l.lunak@kde.org");
m_aboutData.addAuthor(i18n("Maks Orlovich"), i18n("Developer (HTML rendering engine, JavaScript)"), "maksim@kde.org");
m_aboutData.addAuthor(i18n("Allan Sandfeld Jensen"), i18n("Developer (HTML rendering engine)"), "kde@carewolf.com");
m_aboutData.addAuthor(i18n("Apple Safari Developers"), i18n("Developer (HTML rendering engine, JavaScript)"));
m_aboutData.addAuthor(i18n("Harri Porten"), i18n("Developer (JavaScript)"), "porten@kde.org");
m_aboutData.addAuthor(i18n("Koos Vriezen"), i18n("Developer (Java applets and other embedded objects)"), "koos.vriezen@xs4all.nl");
m_aboutData.addAuthor(i18n("Matt Koss"), i18n("Developer (I/O library)"), "koss@miesto.sk");
m_aboutData.addAuthor(i18n("Alex Zepeda"), i18n("Developer (I/O library)"), "zipzippy@sonic.net");
m_aboutData.addAuthor(i18n("Richard Moore"), i18n("Developer (Java applet support)"), "rich@kde.org");
m_aboutData.addAuthor(i18n("Dima Rogozin"), i18n("Developer (Java applet support)"), "dima@mercury.co.il");
m_aboutData.addAuthor(i18n("Wynn Wilkes"), i18n("Developer (Java 2 security manager support,\n and other major improvements to applet support)"), "wynnw@calderasystems.com");
m_aboutData.addAuthor(i18n("Stefan Schimanski"), i18n("Developer (Netscape plugin support)"), "schimmi@kde.org");
m_aboutData.addAuthor(i18n("George Staikos"), i18n("Developer (SSL, Netscape plugins)"), "staikos@kde.org");
m_aboutData.addAuthor(i18n("Dawit Alemayehu"), i18n("Developer (I/O library, Authentication support)"), "adawit@kde.org");
m_aboutData.addAuthor(i18n("Carsten Pfeiffer"), i18n("Developer (framework)"), "pfeiffer@kde.org");
m_aboutData.addAuthor(i18n("Torsten Rahn"), i18n("Graphics/icons"), "torsten@kde.org");
m_aboutData.addAuthor(i18n("Torben Weis"), i18n("KFM author"), "weis@kde.org");
m_aboutData.addAuthor(i18n("Joseph Wenninger"), i18n("Developer (navigation panel framework)"), "jowenn@kde.org");
m_aboutData.addAuthor(i18n("Stephan Binner"), i18n("Developer (misc stuff)"), "binner@kde.org");
m_aboutData.addAuthor(i18n("Ivor Hewitt"), i18n("Developer (AdBlock filter)"), "ivor@ivor.org");
m_aboutData.addAuthor(i18n("Eduardo Robles Elvira"), i18n("Developer (framework)"), "edulix@gmail.com");
KAboutData::setApplicationData(m_aboutData);
}
void KonquerorApplication::setupParser()
{
m_parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
m_aboutData.setupCommandLine(&m_parser);
m_parser.addOption(QCommandLineOption(QStringList{QStringLiteral("silent")}, i18n("Start without a default window, when called without URLs")));
m_parser.addOption(QCommandLineOption(QStringList{QStringLiteral("preload")}, i18n("Preload for later use. This mode does not support URLs on the command line")));
m_parser.addOption(QCommandLineOption(QStringList{QStringLiteral("profile")}, i18n("Profile to open (DEPRECATED, IGNORED)"), i18n("profile")));
m_parser.addOption(QCommandLineOption(QStringList{QStringLiteral("sessions")}, i18n("List available sessions")));
m_parser.addOption(QCommandLineOption(QStringList{QStringLiteral("open-session")}, i18n("Session to open"), i18n("session")));
m_parser.addOption(QCommandLineOption(QStringList{QStringLiteral("mimetype")}, i18n("Mimetype to use for this URL (e.g. text/html or inode/directory)"), i18n("mimetype")));
m_parser.addOption(QCommandLineOption(QStringList{QStringLiteral("part")}, i18n("Part to use (e.g. khtml or kwebkitpart)"), i18n("service")));
m_parser.addOption(QCommandLineOption(QStringList{QStringLiteral("select")}, i18n("For URLs that point to files, opens the directory and selects the file, instead of opening the actual file")));
m_parser.addOption(QCommandLineOption(QStringList{QStringLiteral("tempfile")}, i18n("The files/URLs opened by the application will be deleted after use")));
m_parser.addPositionalArgument(QStringLiteral("[URL]"), i18n("Location to open"));
}
static void fixOldStartUrl() {
QUrl startUrl(KonqSettings::startURL());
if (startUrl.scheme() == "about") {
startUrl.setScheme(KonqUrl::scheme());
KonqSettings::setStartURL(startUrl.url());
KonqSettings::self()->save();
}
}
int KonquerorApplication::startFirstInstance()
{
fixOldStartUrl();
if (isSessionRestored()) {
restoreSession();
} else {
performStart(QDir::currentPath(), true);
}
QString programName = QApplication::applicationFilePath();
const int ret = exec();
bool alwaysPreload = KonqSettings::alwaysHavePreloaded();
// Delete all KonqMainWindows, so that we don't have
// any parts loaded when KLibLoader::cleanUp is called.
// (and Qt doesn't delete WA_DeleteOnClose widgets on exit anyway :()
while (KonqMainWindow::mainWindowList() != nullptr) {
// the list will be deleted by last KonqMainWindow
delete KonqMainWindow::mainWindowList()->first();
}
// Notify the session manager that the instance was closed without errors, and normally.
KonqSessionManager::self()->disableAutosave();
KonqSessionManager::self()->deleteOwnedSessions();
KonqClosedWindowsManager::destroy();
if (alwaysPreload) {
QProcess::startDetached(programName, {"--preload"});
}
return ret;
}
int KonquerorApplication::start()
{
setupAboutData();
setupParser();
KCrash::initialize();
m_parser.process(*this);
m_aboutData.processCommandLine(&m_parser);
KDBusService dbusService(KDBusService::Unique);
auto activateApp = [this](const QStringList &arguments, const QString &workingDirectory) {
m_parser.parse(arguments);
performStart(workingDirectory, false);
};
QObject::connect(&dbusService, &KDBusService::activateRequested, activateApp);
return startFirstInstance();
}
int KonquerorApplication::performStart(const QString& workingDirectory, bool firstInstance)
{
const QStringList args = m_parser.positionalArguments();
if (m_parser.isSet("sessions")) {
listSessions();
return 0;
} else if (m_parser.isSet("open-session")) {
return openSession(m_parser.value("open-session"));
}
//We check for the --preload switch before attempting recovering session because we shouldn't
//display windows when the user only asked to preload a window
if (m_parser.isSet("preload")) {
preloadWindow(args);
return 0;
}
if (!m_sessionRecoveryAttempted) {
// Ask the user to recover session if applicable
KonqSessionManager::self()->askUserToRestoreAutosavedAbandonedSessions();
m_sessionRecoveryAttempted = true;
}
WindowCreationResult result;
if (args.isEmpty()) {
result = createEmptyWindow(firstInstance);
} else {
result = createWindowsForUrlArguments(args, workingDirectory);
}
KonqMainWindow *mw = result.first;
if (!firstInstance && mw) {
#if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5,62,0)
mw ->setAttribute(Qt::WA_NativeWindow, true);
KStartupInfo::setNewStartupId(mw->windowHandle(), KStartupInfo::startupId());
#else
KStartupInfo::setNewStartupId(mw, KStartupInfo::startupId());
#endif
KWindowSystem::forceActiveWindow(mw->winId());
}
return result.second;
}
KonquerorApplication::WindowCreationResult KonquerorApplication::createWindowsForUrlArguments(const QStringList& args, const QString &workingDirectory)
{
QList<QUrl> urlList;
urlList.reserve(args.length());
auto urlFromArg = [workingDirectory](const QString &arg) {
const QUrl url = QUrl::fromUserInput(arg, workingDirectory);
// KonqMisc::konqFilteredURL doesn't cope with local files... A bit of hackery below
if (url.isLocalFile() && QFile::exists(url.toLocalFile())) { // "konqueror index.html"
return url;
} else { // "konqueror slashdot.org"
return KonqMisc::konqFilteredURL(nullptr, arg);
}
};
for (const QString &arg : args) {
urlList.append(urlFromArg(arg));
};
QList<QUrl> filesToSelect;
if (m_parser.isSet("select")) {
// Get all distinct directories from 'files' and open a tab
// for each directory.
QList<QUrl> dirs;
for (const QUrl &url : urlList) {
const QUrl dir(url.adjusted(QUrl::RemoveFilename));
if (!dirs.contains(dir)) {
dirs.append(dir);
}
}
filesToSelect = urlList;
urlList = dirs;
}
QUrl firstUrl = urlList.takeFirst();
KParts::OpenUrlArguments urlargs;
if (m_parser.isSet("mimetype")) {
urlargs.setMimeType(m_parser.value("mimetype"));
}
KonqOpenURLRequest req;
req.args = urlargs;
req.filesToSelect = filesToSelect;
req.tempFile = m_parser.isSet("tempfile");
req.serviceName = m_parser.value("part");
KonqMainWindow *mainwin = KonqMainWindowFactory::createNewWindow(firstUrl, req);
if (!mainwin) {
return qMakePair(nullptr, 1);
}
mainwin->show();
if (!urlList.isEmpty()) {
// Open the other urls as tabs in that window
mainwin->openMultiURL(urlList);
}
return qMakePair(mainwin, 0);
}
void KonquerorApplication::preloadWindow(const QStringList &args)
{
if (!args.isEmpty()) {
QTextStream ts(stderr, QIODevice::WriteOnly);
ts << i18n("You can't pass URLs when using the --preload switch. The URLs will be ignored\n");
}
KonqMainWindowFactory::createPreloadWindow();
}
KonquerorApplication::WindowCreationResult KonquerorApplication::createEmptyWindow(bool firstInstance)
{
//Always create a new window except when called with the --silent switch or a session has been recovered (see #388333)
if (m_parser.isSet("silent")) {
return qMakePair(nullptr, 0);
}
if (firstInstance) { // If session recovery created some windows, no need for an empty window here.
QList<KonqMainWindow *> *mainWindowList = KonqMainWindow::mainWindowList();
if (mainWindowList && !mainWindowList->isEmpty()) {
return qMakePair(mainWindowList->at(0), 0);
}
}
KonqMainWindow *mainWin = KonqMainWindowFactory::createNewWindow();
if (mainWin) {
mainWin->show();
return qMakePair(mainWin, 0);
} else {
return qMakePair(nullptr, 1);
}
}
void KonquerorApplication::listSessions()
{
const QString dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + "sessions/";
QDirIterator it(dir, QDir::Readable | QDir::NoDotAndDotDot | QDir::Dirs);
QTextStream ts(stdout, QIODevice::WriteOnly);
while (it.hasNext()) {
QFileInfo fileInfo(it.next());
ts << fileInfo.baseName();
}
}
int KonquerorApplication::openSession(const QString& session)
{
QString sessionPath = session;
if (!session.startsWith('/')) {
sessionPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + "sessions/" + session;
}
QDirIterator it(sessionPath, QDir::Readable | QDir::Files);
if (!it.hasNext()) {
qCWarning(KONQUEROR_LOG) << "session" << session << "not found or empty";
return 1;
}
KonqSessionManager::self()->restoreSessions(sessionPath);
QList<KonqMainWindow*> *mainWindows = KonqMainWindow::mainWindowList();
return (mainWindows && !mainWindows->isEmpty()) ? 0 : 1;
}
void KonquerorApplication::restoreSession()
{
KonqSessionManager::self()->restoreSessionSavedAtLogout();
}
......@@ -10,6 +10,11 @@
#include "konqprivate_export.h"
#include <QApplication>
#include <KAboutData>
#include <QCommandLineParser>
class KonqMainWindow;
class QDBusMessage;
class KONQ_TESTS_EXPORT KonquerorApplication : public QApplication
......@@ -17,6 +22,7 @@ class KONQ_TESTS_EXPORT KonquerorApplication : public QApplication
Q_OBJECT
public:
KonquerorApplication(int &argc, char **argv);
int start();
public slots:
void slotReparseConfiguration();
......@@ -25,6 +31,27 @@ private slots:
void slotAddToCombo(const QString &url, const QDBusMessage &msg);
void slotRemoveFromCombo(const QString &url, const QDBusMessage &msg);
void slotComboCleared(const QDBusMessage &msg);
private:
using WindowCreationResult = QPair<KonqMainWindow*, int>;
void setupAboutData();
void setupParser();
int startFirstInstance();
int performStart(const QString &workingDirectory, bool firstInstance = false);
void restoreSession();
void listSessions();
int openSession(const QString &session);
WindowCreationResult createEmptyWindow(bool firstInstance);
void preloadWindow(const QStringList &args);
WindowCreationResult createWindowsForUrlArguments(const QStringList &args, const QString &workingDirectory);
private:
KAboutData m_aboutData;
QCommandLineParser m_parser;
bool m_sessionRecoveryAttempted = false;
};
#endif
......@@ -16,12 +16,8 @@
#include "konqurl.h"
#include "konqclosedwindowsmanager.h"
#include <KAboutData>
#include <KCrash>
#include <KLocalizedString>
#include <config-konqueror.h>
#include <konqueror-version.h>
#include "konqdebug.h"
#include <QFile>
......@@ -31,270 +27,15 @@
#include <QProcess>
#include <KDBusService>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <KStartupInfo>
#include <KWindowSystem>
#include <kwindowsystem_version.h>
static void listSessions()
{
const QString dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + "sessions/";
QDirIterator it(dir, QDir::Readable | QDir::NoDotAndDotDot | QDir::Dirs);
while (it.hasNext()) {
QFileInfo fileInfo(it.next());
printf("%s\n", QFile::encodeName(fileInfo.baseName()).constData());
}
}
static KonqMainWindow* handleCommandLine(QCommandLineParser &parser, const QString &workingDirectory, int *ret)
{
*ret = 0;
const QStringList args = parser.positionalArguments();
qCDebug(KONQUEROR_LOG) << "args=" << args;
// First the invocations that do not take urls.
if (parser.isSet("sessions")) {
listSessions();
return nullptr;
} else if (parser.isSet("open-session")) {
const QString session = parser.value("open-session");
QString sessionPath = session;
if (!session.startsWith('/')) {
sessionPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + "sessions/" + session;
}
QDirIterator it(sessionPath, QDir::Readable | QDir::Files);
if (!it.hasNext()) {
qCWarning(KONQUEROR_LOG) << "session" << session << "not found or empty";
*ret = 1;
return nullptr;
}
KonqSessionManager::self()->restoreSessions(sessionPath);
if (!KonqMainWindow::mainWindowList()->isEmpty()) {
return KonqMainWindow::mainWindowList()->first();
} else {
return nullptr;
}
}
const bool firstStart = !KonqMainWindow::mainWindowList();
// Ask the user to recover session if applicable
KonqSessionManager::self()->askUserToRestoreAutosavedAbandonedSessions();
if (args.isEmpty()) {
// No args. If --silent, do nothing, otherwise create a default window.
if (!parser.isSet("silent")) {
// #388333
if (firstStart) {
// If session recovery created some windows, no need for an empty window here.
QList<KonqMainWindow *> *mainWindowList = KonqMainWindow::mainWindowList();
if (mainWindowList && !mainWindowList->isEmpty()) {
return mainWindowList->at(0);
}
}
KonqMainWindow *mainWin = KonqMainWindowFactory::createNewWindow();
mainWin->show();
return mainWin;
}
} else {
// Now is a good time to parse each argument as a URL.
QList<QUrl> urlList;
for (int i = 0; i < args.count(); i++) {
// KonqMisc::konqFilteredURL doesn't cope with local files... A bit of hackery below
const QUrl url = QUrl::fromUserInput(args.at(i), workingDirectory);
if (url.isLocalFile() && QFile::exists(url.toLocalFile())) { // "konqueror index.html"
urlList += url;
} else {
urlList += KonqMisc::konqFilteredURL(nullptr, args.at(i)); // "konqueror slashdot.org"
}
}
QList<QUrl> filesToSelect;
if (parser.isSet("select")) {
// Get all distinct directories from 'files' and open a tab
// for each directory.
QList<QUrl> dirs;
Q_FOREACH (const QUrl &url, urlList) {
const QUrl dir(url.adjusted(QUrl::RemoveFilename));
if (!dirs.contains(dir)) {
dirs.append(dir);
}
}
filesToSelect = urlList;
urlList = dirs;
}
QUrl firstUrl = urlList.takeFirst();
KParts::OpenUrlArguments urlargs;
if (parser.isSet("mimetype")) {
urlargs.setMimeType(parser.value("mimetype"));
}
KonqOpenURLRequest req;
req.args = urlargs;
req.filesToSelect = filesToSelect;
req.tempFile = parser.isSet("tempfile");
req.serviceName = parser.value("part");
KonqMainWindow *mainwin = KonqMainWindowFactory::createNewWindow(firstUrl, req);
mainwin->show();
if (!urlList.isEmpty()) {
// Open the other urls as tabs in that window
mainwin->openMultiURL(urlList);
}
return mainwin;
}
return nullptr;
}
static void fixOldStartUrl() {
QUrl startUrl(KonqSettings::startURL());
if (startUrl.scheme() == "about") {
startUrl.setScheme(KonqUrl::scheme());
KonqSettings::setStartURL(startUrl.url());
KonqSettings::self()->save();
}
}
#include <KMessageBox>
extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv)
{
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // says QtWebEngine
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // says QtWebEngine. Note: this must be set before creating the application
KonquerorApplication app(argc, argv);
KLocalizedString::setApplicationDomain("konqueror");
KAboutData aboutData("konqueror", i18n("Konqueror"), KONQUEROR_VERSION);
aboutData.setShortDescription(i18n("Web browser, file manager and document viewer."));
aboutData.addLicense(KAboutLicense::GPL_V2);
aboutData.setCopyrightStatement(i18n("(C) 1999-2016, The Konqueror developers"));
aboutData.setHomepage("https://konqueror.org/");
aboutData.addAuthor(i18n("Stefano Crocco"), i18n("Current maintainer"), "stefano.crocco@alice.it");
aboutData.addAuthor(i18n("David Faure"), i18n("Developer (framework, parts, JavaScript, I/O library) and former maintainer"), "faure@kde.org");
aboutData.addAuthor(i18n("Simon Hausmann"), i18n("Developer (framework, parts)"), "hausmann@kde.org");
aboutData.addAuthor(i18n("Michael Reiher"), i18n("Developer (framework)"), "michael.reiher@gmx.de");
aboutData.addAuthor(i18n("Matthias Welk"), i18n("Developer"), "welk@fokus.gmd.de");
aboutData.addAuthor(i18n("Alexander Neundorf"), i18n("Developer (List views)"), "neundorf@kde.org");
aboutData.addAuthor(i18n("Michael Brade"), i18n("Developer (List views, I/O library)"), "brade@kde.org");
aboutData.addAuthor(i18n("Lars Knoll"), i18n("Developer (HTML rendering engine)"), "knoll@kde.org");
aboutData.addAuthor(i18n("Dirk Mueller"), i18n("Developer (HTML rendering engine)"), "mueller@kde.org");
aboutData.addAuthor(i18n("Peter Kelly"), i18n("Developer (HTML rendering engine)"), "pmk@post.com");
aboutData.addAuthor(i18n("Waldo Bastian"), i18n("Developer (HTML rendering engine, I/O library)"), "bastian@kde.org");
aboutData.addAuthor(i18n("Germain Garand"), i18n("Developer (HTML rendering engine)"), "germain@ebooksfrance.org");
aboutData.addAuthor(i18n("Leo Savernik"), i18n("Developer (HTML rendering engine)"), "l.savernik@aon.at");
aboutData.addAuthor(i18n("Stephan Kulow"), i18n("Developer (HTML rendering engine, I/O library, regression test framework)"), "coolo@kde.org");
aboutData.addAuthor(i18n("Antti Koivisto"), i18n("Developer (HTML rendering engine)"), "koivisto@kde.org");
aboutData.addAuthor(i18n("Zack Rusin"), i18n("Developer (HTML rendering engine)"), "zack@kde.org");
aboutData.addAuthor(i18n("Tobias Anton"), i18n("Developer (HTML rendering engine)"), "anton@stud.fbi.fh-darmstadt.de");
aboutData.addAuthor(i18n("Lubos Lunak"), i18n("Developer (HTML rendering engine)"), "l.lunak@kde.org");
aboutData.addAuthor(i18n("Maks Orlovich"), i18n("Developer (HTML rendering engine, JavaScript)"), "maksim@kde.org");
aboutData.addAuthor(i18n("Allan Sandfeld Jensen"), i18n("Developer (HTML rendering engine)"), "kde@carewolf.com");
aboutData.addAuthor(i18n("Apple Safari Developers"), i18n("Developer (HTML rendering engine, JavaScript)"));
aboutData.addAuthor(i18n("Harri Porten"), i18n("Developer (JavaScript)"), "porten@kde.org");
aboutData.addAuthor(i18n("Koos Vriezen"), i18n("Developer (Java applets and other embedded objects)"), "koos.vriezen@xs4all.nl");
aboutData.addAuthor(i18n("Matt Koss"), i18n("Developer (I/O library)"), "koss@miesto.sk");
aboutData.addAuthor(i18n("Alex Zepeda"), i18n("Developer (I/O library)"), "zipzippy@sonic.net");
aboutData.addAuthor(i18n("Richard Moore"), i18n("Developer (Java applet support)"), "rich@kde.org");
aboutData.addAuthor(i18n("Dima Rogozin"), i18n("Developer (Java applet support)"), "dima@mercury.co.il");
aboutData.addAuthor(i18n("Wynn Wilkes"), i18n("Developer (Java 2 security manager support,\n and other major improvements to applet support)"), "wynnw@calderasystems.com");
aboutData.addAuthor(i18n("Stefan Schimanski"), i18n("Developer (Netscape plugin support)"), "schimmi@kde.org");
aboutData.addAuthor(i18n("George Staikos"), i18n("Developer (SSL, Netscape plugins)"), "staikos@kde.org");
aboutData.addAuthor(i18n("Dawit Alemayehu"), i18n("Developer (I/O library, Authentication support)"), "adawit@kde.org");
aboutData.addAuthor(i18n("Carsten Pfeiffer"), i18n("Developer (framework)"), "pfeiffer@kde.org");
aboutData.addAuthor(i18n("Torsten Rahn"), i18n("Graphics/icons"), "torsten@kde.org");
aboutData.addAuthor(i18n("Torben Weis"), i18n("KFM author"), "weis@kde.org");
aboutData.addAuthor(i18n("Joseph Wenninger"), i18n("Developer (navigation panel framework)"), "jowenn@kde.org");