File indexing completed on 2024-04-21 04:36:22

0001 /*
0002     SPDX-FileCopyrightText: 2003-2009 Alexander Dymo <adymo@kdevelop.org>
0003     SPDX-FileCopyrightText: 2007 Ralf Habacker <Ralf.Habacker@freenet.de>
0004     SPDX-FileCopyrightText: 2006-2007 Matt Rogers <mattr@kde.org>
0005     SPDX-FileCopyrightText: 2006-2007 Hamish Rodda <rodda@kde.org>
0006     SPDX-FileCopyrightText: 2005-2007 Adam Treat <treat@kde.org>
0007     SPDX-FileCopyrightText: 2003-2007 Jens Dagerbo <jens.dagerbo@swipnet.se>
0008     SPDX-FileCopyrightText: 2001-2002 Bernd Gehrmann <bernd@mail.berlios.de>
0009     SPDX-FileCopyrightText: 2001-2002 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
0010     SPDX-FileCopyrightText: 2003 Roberto Raggi <roberto@kdevelop.org>
0011     SPDX-FileCopyrightText: 2010 Niko Sams <niko.sams@gmail.com>
0012     SPDX-FileCopyrightText: 2015 Kevin Funk <kfunk@kde.org>
0013 
0014     SPDX-License-Identifier: LGPL-2.0-or-later
0015 */
0016 
0017 #include "config-kdevelop.h"
0018 #include "kdevelop_version.h"
0019 
0020 #include "urlinfo.h"
0021 
0022 #include <KLocalizedString>
0023 #include <Kdelibs4ConfigMigrator>
0024 #include <KAboutData>
0025 #include <KCrash>
0026 
0027 #include <QApplication>
0028 #include <QElapsedTimer>
0029 #include <QCommandLineParser>
0030 #include <QCommandLineOption>
0031 #include <QFileInfo>
0032 #include <QProcessEnvironment>
0033 #include <QSessionManager>
0034 #include <QTextStream>
0035 #include <QDBusInterface>
0036 #include <QDBusReply>
0037 
0038 #include <QQuickWindow>
0039 
0040 #include <shell/core.h>
0041 #include <shell/mainwindow.h>
0042 #include <shell/projectcontroller.h>
0043 #include <shell/documentcontroller.h>
0044 #include <shell/plugincontroller.h>
0045 #include <shell/sessioncontroller.h>
0046 #include <shell/runcontroller.h>
0047 #include <shell/launchconfiguration.h>
0048 #include <shell/session.h>
0049 #include <interfaces/ilauncher.h>
0050 #include <interfaces/iproject.h>
0051 #include <interfaces/launchconfigurationtype.h>
0052 #include <util/path.h>
0053 #include <debug.h>
0054 
0055 #include "kdevideextension.h"
0056 #if KDEVELOP_SINGLE_APP
0057 #include "qtsingleapplication.h"
0058 #endif
0059 
0060 #include <iostream>
0061 
0062 #ifdef Q_OS_MAC
0063 #include <CoreFoundation/CoreFoundation.h>
0064 #endif
0065 
0066 using namespace KDevelop;
0067 
0068 namespace {
0069 
0070 #if KDEVELOP_SINGLE_APP
0071 QString serializeOpenFilesMessage(const QVector<UrlInfo> &infos)
0072 {
0073     QByteArray message;
0074     QDataStream stream(&message, QIODevice::WriteOnly);
0075     stream << QByteArrayLiteral("open");
0076     stream << infos;
0077     return QString::fromLatin1(message.toHex());
0078 }
0079 #endif
0080 
0081 void openFiles(const QVector<UrlInfo>& infos)
0082 {
0083     for (const UrlInfo& info : infos) {
0084         if (!ICore::self()->documentController()->openDocument(info.url, info.cursor)) {
0085             qCWarning(APP) << i18n("Could not open %1", info.url.toDisplayString(QUrl::PreferLocalFile));
0086         }
0087     }
0088 }
0089 
0090 }
0091 
0092 class KDevelopApplication:
0093 #if KDEVELOP_SINGLE_APP
0094     public SharedTools::QtSingleApplication
0095 #else
0096     public QApplication
0097 #endif
0098 {
0099     Q_OBJECT
0100 public:
0101     explicit KDevelopApplication(int &argc, char **argv, bool GUIenabled = true)
0102 #if KDEVELOP_SINGLE_APP
0103         : SharedTools::QtSingleApplication(QStringLiteral("KDevelop"), argc, argv)
0104 #else
0105         : QApplication(argc, argv, GUIenabled)
0106 #endif
0107         {
0108             Q_UNUSED(GUIenabled);
0109             connect(this, &QGuiApplication::saveStateRequest, this, &KDevelopApplication::saveState);
0110         }
0111 
0112 #if KDEVELOP_SINGLE_APP
0113 public Q_SLOTS:
0114     void remoteArguments(const QString &message, QObject *socket)
0115     {
0116         Q_UNUSED(socket);
0117 
0118         QByteArray ba = QByteArray::fromHex(message.toLatin1());
0119         QDataStream stream(ba);
0120         QByteArray command;
0121         stream >> command;
0122 
0123         qCDebug(APP) << "Received remote command: " << command;
0124 
0125         if (command == "open") {
0126             QVector<UrlInfo> infos;
0127             stream >> infos;
0128 
0129             QVector<UrlInfo> files, directories;
0130             for (const auto& info : infos)
0131                 if (info.isDirectory())
0132                     directories << info;
0133                 else
0134                     files << info;
0135 
0136             openFiles(files);
0137             for(const auto &urlinfo : directories)
0138                 ICore::self()->projectController()->openProjectForUrl(urlinfo.url);
0139         } else {
0140             qCWarning(APP) << "Unknown remote command: " << command;
0141         }
0142     }
0143 
0144     void fileOpenRequested(const QString &file)
0145     {
0146         openFiles({UrlInfo(file)});
0147     }
0148 #endif
0149 
0150 private Q_SLOTS:
0151     void saveState( QSessionManager& sm ) {
0152         if (KDevelop::Core::self() && KDevelop::Core::self()->sessionController()) {
0153             const auto activeSession = KDevelop::Core::self()->sessionController()->activeSession();
0154             if (!activeSession) {
0155                 qCWarning(APP) << "No active session, can't save state";
0156                 return;
0157             }
0158 
0159             const QString x11SessionId = sm.sessionId() + QLatin1Char('_') + sm.sessionKey();
0160             QString kdevelopSessionId = activeSession->id().toString();
0161             sm.setRestartCommand({
0162                 QCoreApplication::applicationFilePath(),
0163                 QStringLiteral("-session"),
0164                 x11SessionId,
0165                 QStringLiteral("-s"),
0166                 kdevelopSessionId
0167             });
0168         }
0169     }
0170 };
0171 
0172 /// Tries to find a session identified by @p data in @p sessions.
0173 /// The @p data may be either a session's name or a string-representation of its UUID.
0174 /// @return pointer to the session or NULL if nothing appropriate has been found
0175 static const KDevelop::SessionInfo* findSessionInList( const SessionInfos& sessions, const QString& data )
0176 {
0177     // We won't search a session without input data, since that could lead to false-positives
0178     // with unnamed sessions
0179     if( data.isEmpty() )
0180         return nullptr;
0181 
0182     for( auto it = sessions.constBegin(); it != sessions.constEnd(); ++it ) {
0183         if ( ( it->name == data ) || ( it->uuid.toString() == data ) ) {
0184             const KDevelop::SessionInfo& sessionRef = *it;
0185             return &sessionRef;
0186         }
0187     }
0188     return nullptr;
0189 }
0190 
0191 /// Tries to find sessions containing project @p projectUrl in @p sessions.
0192 static const KDevelop::SessionInfos findSessionsWithProject(const SessionInfos& sessions, const QUrl& projectUrl)
0193 {
0194     if (!projectUrl.isValid())
0195         return {};
0196 
0197     KDevelop::SessionInfos infos;
0198     for (auto& session : sessions) {
0199         if (session.projects.contains(projectUrl)) {
0200             infos << session;
0201         }
0202     }
0203     return infos;
0204 }
0205 
0206 /// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid
0207 /// Returns the exit status
0208 static int openFilesInRunningInstance(const QVector<UrlInfo>& files, qint64 pid)
0209 {
0210     const QString service = QStringLiteral("org.kdevelop.kdevelop-%1").arg(pid);
0211     QDBusInterface iface(service, QStringLiteral("/org/kdevelop/DocumentController"), QStringLiteral("org.kdevelop.DocumentController"));
0212 
0213     QStringList urls;
0214     bool errors_occurred = false;
0215     for (const UrlInfo& file : files) {
0216         QDBusReply<bool> result = iface.call(QStringLiteral("openDocumentSimple"), file.url.toString(), file.cursor.line(), file.cursor.column());
0217         if ( ! result.value() ) {
0218             QTextStream err(stderr);
0219             err << i18n("Could not open file '%1'.", file.url.toDisplayString(QUrl::PreferLocalFile)) << "\n";
0220             errors_occurred = true;
0221         }
0222     }
0223     // make the window visible
0224     QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"),
0225                                                                QStringLiteral("ensureVisible") );
0226     QDBusConnection::sessionBus().asyncCall( makeVisible );
0227     return errors_occurred;
0228 }
0229 
0230 /// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid
0231 /// Returns the exit status
0232 static int openProjectInRunningInstance(const QVector<UrlInfo>& paths, qint64 pid)
0233 {
0234     const QString service = QStringLiteral("org.kdevelop.kdevelop-%1").arg(pid);
0235     QDBusInterface iface(service, QStringLiteral("/org/kdevelop/ProjectController"), QStringLiteral("org.kdevelop.ProjectController"));
0236     int errors = 0;
0237 
0238     for (const UrlInfo& path : paths) {
0239         QDBusReply<void> result = iface.call(QStringLiteral("openProjectForUrl"), path.url.toString());
0240         if ( !result.isValid() ) {
0241             QTextStream err(stderr);
0242             err << i18n("Could not open project '%1': %2", path.url.toDisplayString(QUrl::PreferLocalFile), result.error().message()) << "\n";
0243             ++errors;
0244         }
0245     }
0246     // make the window visible
0247     QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"),
0248                                                                QStringLiteral("ensureVisible") );
0249     QDBusConnection::sessionBus().asyncCall( makeVisible );
0250     return errors;
0251 }
0252 
0253 /// Gets the PID of a running KDevelop instance, eventually asking the user if there is more than one.
0254 /// Returns -1 in case there are no running sessions.
0255 static qint64 getRunningSessionPid()
0256 {
0257     SessionInfos candidates;
0258     const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos();
0259     for (const KDevelop::SessionInfo& si : availableSessionInfos) {
0260         if( KDevelop::SessionController::isSessionRunning(si.uuid.toString()) ) {
0261             candidates << si;
0262         }
0263     }
0264     if ( candidates.isEmpty() ) {
0265         return -1;
0266     }
0267 
0268     QString sessionUuid;
0269     if ( candidates.size() == 1 ) {
0270         sessionUuid = candidates.first().uuid.toString();
0271     }
0272     else {
0273         const QString title = i18n("Select the session to open the document in");
0274         sessionUuid = KDevelop::SessionController::showSessionChooserDialog(title, true);
0275     }
0276     return KDevelop::SessionController::sessionRunInfo(sessionUuid).holderPid;
0277 }
0278 
0279 static QString findSessionId(const SessionInfos& availableSessionInfos, const QString& session)
0280 {
0281     //If there is a session and a project with the same name, always open the session
0282     //regardless of the order encountered
0283     QString projectAsSession;
0284     for (const KDevelop::SessionInfo& si : availableSessionInfos) {
0285         if ( session == si.name || session == si.uuid.toString() ) {
0286             return si.uuid.toString();
0287         } else if (projectAsSession.isEmpty()) {
0288             for (const QUrl& k : si.projects) {
0289                 QString fn(k.fileName());
0290                 fn = fn.left(fn.indexOf(QLatin1Char('.')));
0291                 if ( session == fn ) {
0292                     projectAsSession = si.uuid.toString();
0293                 }
0294             }
0295         }
0296     }
0297 
0298     if (projectAsSession.isEmpty())  {
0299         QTextStream qerr(stderr);
0300         qerr << QLatin1Char('\n') << i18n("Cannot open unknown session %1. See `--list-sessions` switch for available sessions or use `-n` to create a new one.",
0301                              session) << QLatin1Char('\n');
0302     }
0303     return projectAsSession;
0304 }
0305 
0306 static qint64 findSessionPid(const QString &sessionId)
0307 {
0308     KDevelop::SessionRunInfo sessionInfo = KDevelop::SessionController::sessionRunInfo( sessionId );
0309     return sessionInfo.holderPid;
0310 }
0311 
0312 int main( int argc, char *argv[] )
0313 {
0314     QElapsedTimer timer;
0315     timer.start();
0316 
0317     // If possible, use the Software backend for QQuickWidget (currently used in the
0318     // welcome page plugin). This means we don't need OpenGL at all, avoiding issues
0319     // like https://bugs.kde.org/show_bug.cgi?id=386527.
0320     QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software);
0321 
0322     // Useful for valgrind runs, just `export KDEV_DISABLE_JIT=1`
0323     if (qEnvironmentVariableIsSet("KDEV_DISABLE_JIT")) {
0324         qputenv("KDEV_DISABLE_WELCOMEPAGE", "1");
0325         qputenv("QT_ENABLE_REGEXP_JIT", "0");
0326     }
0327 
0328     QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
0329     QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
0330 
0331 #ifdef Q_OS_MAC
0332     CFBundleRef mainBundle = CFBundleGetMainBundle();
0333     if (mainBundle) {
0334         // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist,
0335         // for regular executables it is obtained in another way.
0336         CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle);
0337         if (infoDict) {
0338             // Try to prevent App Nap on OS X. This can be tricky in practice, at least in 10.9 .
0339             CFDictionarySetValue(infoDict, CFSTR("NSAppSleepDisabled"), kCFBooleanTrue);
0340             CFDictionarySetValue(infoDict, CFSTR("NSSupportsAutomaticTermination"), kCFBooleanFalse);
0341         }
0342     }
0343 #endif
0344 
0345     //we can't use KCmdLineArgs as it doesn't allow arguments for the debugee
0346     //so lookup the --debug switch and eat everything behind by decrementing argc
0347     //debugArgs is filled with args after --debug <debuger>
0348     QStringList debugArgs;
0349     QString debugeeName;
0350     {
0351         bool debugFound = false;
0352         int c = argc;
0353         for (int i=0; i < c; ++i) {
0354             if (debugFound) {
0355                 debugArgs << QString::fromUtf8(argv[i]);
0356             } else if ((qstrcmp(argv[i], "--debug") == 0) || (qstrcmp(argv[i], "-d") == 0)) {
0357                 if (argc > (i + 1)) {
0358                     i++;
0359                 }
0360                 argc = i + 1;
0361                 debugFound = true;
0362             } else if (QByteArray(argv[i]).startsWith("--debug=")) {
0363                 argc = i + 1;
0364                 debugFound = true;
0365             }
0366         }
0367     }
0368 
0369     KDevelopApplication app(argc, argv);
0370     // Prevent SIGPIPE, then "ICE default IO error handler doing an exit(), pid = <PID>, errno = 32"
0371     // crash when the first event loop starts at least 60 seconds after KDevelop launch. This can
0372     // happen during a Debug Launch of KDevelop from KDevelop, especially if a breakpoint is hit
0373     // before any event loop is entered.
0374     QCoreApplication::processEvents();
0375 
0376     KLocalizedString::setApplicationDomain("kdevelop");
0377 
0378     KAboutData aboutData(QStringLiteral("kdevelop"), i18n("KDevelop"),
0379                          QStringLiteral(KDEVELOP_VERSION_STRING " (" RELEASE_SERVICE_VERSION_STRING ")"),
0380                          i18n("The KDevelop Integrated Development Environment"), KAboutLicense::GPL,
0381                          i18n("Copyright 1999-%1, The KDevelop developers", QStringLiteral("2023")), QString(),
0382                          QStringLiteral("https://www.kdevelop.org/"));
0383     aboutData.setDesktopFileName(QStringLiteral("org.kde.kdevelop"));
0384     aboutData.addAuthor( i18n("Kevin Funk"), i18n( "Co-maintainer, C++/Clang, QA, Windows Support, Performance, Website" ), QStringLiteral("kfunk@kde.org") );
0385     aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Co-maintainer, AppImage, Python Support, User Interface improvements" ), QStringLiteral("svenbrauch@gmail.com") );
0386     aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "CMake Support, Run Support, Kross Support" ), QStringLiteral("aleixpol@kde.org") );
0387     aboutData.addAuthor( i18n("Milian Wolff"), i18n( "C++/Clang, Generic manager, Webdevelopment Plugins, Snippets, Performance" ), QStringLiteral("mail@milianw.de") );
0388     aboutData.addAuthor( i18n("Olivier JG"), i18n( "C++/Clang, DUChain, Bug Fixes" ), QStringLiteral("olivier.jg@gmail.com") );
0389     aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), QStringLiteral("apaku@gmx.de") );
0390     aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), QStringLiteral("adymo@kdevelop.org") );
0391     aboutData.addAuthor( i18n("David Nolden"), i18n( "Definition-Use Chain, C++ Support, Code Navigation, Code Completion, Coding Assistance, Refactoring" ), QStringLiteral("david.nolden.kdevelop@art-master.de") );
0392     aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), QStringLiteral("ghost@cs.msu.su") );
0393     aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), QStringLiteral("rodda@kde.org") );
0394     aboutData.addAuthor( i18n("Amilcar do Carmo Lucas"), i18n( "Website admin, API documentation, Doxygen and autoproject patches" ), QStringLiteral("amilcar@kdevelop.org") );
0395     aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), QStringLiteral("niko.sams@gmail.com") );
0396     aboutData.addAuthor( i18n("Friedrich W. H. Kossebau"), QString(), QStringLiteral("kossebau@kde.org") );
0397 
0398     aboutData.addCredit( i18n("Matt Rogers"), QString(), QStringLiteral("mattr@kde.org"));
0399     aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), QStringLiteral("cedric.pasteur@free.fr") );
0400     aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), QStringLiteral("powerfox@kde.ru") );
0401     // QTest integration is separate in playground currently.
0402     //aboutData.addCredit( i18n("Manuel Breugelmanns"), i18n( "Veritas, QTest integration"), "mbr.nxi@gmail.com" );
0403     aboutData.addCredit( i18n("Robert Gruber") , i18n( "SnippetPart, debugger and usability patches" ), QStringLiteral("rgruber@users.sourceforge.net") );
0404     aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), QStringLiteral("dukjuahn@gmail.com") );
0405     aboutData.addCredit( i18n("Harald Fernengel"), i18n( "Ported to Qt 3, patches, valgrind, diff and perforce support" ), QStringLiteral("harry@kdevelop.org") );
0406     aboutData.addCredit( i18n("Roberto Raggi"), i18n( "C++ parser" ), QStringLiteral("roberto@kdevelop.org") );
0407     aboutData.addCredit( i18n("The KWrite authors"), i18n( "Kate editor component" ), QStringLiteral("kwrite-devel@kde.org") );
0408     aboutData.addCredit( i18n("Nokia Corporation/Qt Software"), i18n( "Designer code" ), QStringLiteral("qt-info@nokia.com") );
0409 
0410     aboutData.addCredit( i18n("Contributors to older versions:"), QString(), QString() );
0411     aboutData.addCredit( i18n("Bernd Gehrmann"), i18n( "Initial idea, basic architecture, much initial source code" ), QStringLiteral("bernd@kdevelop.org") );
0412     aboutData.addCredit( i18n("Caleb Tennis"), i18n( "KTabBar, bugfixes" ), QStringLiteral("caleb@aei-tech.com") );
0413     aboutData.addCredit( i18n("Richard Dale"), i18n( "Java & Objective C support" ), QStringLiteral("Richard_Dale@tipitina.demon.co.uk") );
0414     aboutData.addCredit( i18n("John Birch"), i18n( "Debugger frontend" ), QStringLiteral("jbb@kdevelop.org") );
0415     aboutData.addCredit( i18n("Sandy Meier"), i18n( "PHP support, context menu stuff" ), QStringLiteral("smeier@kdevelop.org") );
0416     aboutData.addCredit( i18n("Kurt Granroth"), i18n( "KDE application templates" ), QStringLiteral("kurth@granroth.org") );
0417     aboutData.addCredit( i18n("Ian Reinhart Geiser"), i18n( "Dist part, bash support, application templates" ), QStringLiteral("geiseri@yahoo.com") );
0418     aboutData.addCredit( i18n("Matthias Hoelzer-Kluepfel"), i18n( "Several components, htdig indexing" ), QStringLiteral("hoelzer@kde.org") );
0419     aboutData.addCredit( i18n("Victor Roeder"), i18n( "Help with Automake manager and persistent class store" ), QStringLiteral("victor_roeder@gmx.de") );
0420     aboutData.addCredit( i18n("Simon Hausmann"), i18n( "Help with KParts infrastructure" ), QStringLiteral("hausmann@kde.org") );
0421     aboutData.addCredit( i18n("Oliver Kellogg"), i18n( "Ada support" ), QStringLiteral("okellogg@users.sourceforge.net") );
0422     aboutData.addCredit( i18n("Jakob Simon-Gaarde"), i18n( "QMake projectmanager" ), QStringLiteral("jsgaarde@tdcspace.dk") );
0423     aboutData.addCredit( i18n("Falk Brettschneider"), i18n( "MDI modes, QEditor, bugfixes" ), QStringLiteral("falkbr@kdevelop.org") );
0424     aboutData.addCredit( i18n("Mario Scalas"), i18n( "PartExplorer, redesign of CvsPart, patches, bugs(fixes)" ), QStringLiteral("mario.scalas@libero.it") );
0425     aboutData.addCredit( i18n("Jens Dagerbo"), i18n( "Replace, Bookmarks, FileList and CTags2 plugins. Overall improvements and patches" ), QStringLiteral("jens.dagerbo@swipnet.se") );
0426     aboutData.addCredit( i18n("Julian Rockey"), i18n( "Filecreate part and other bits and patches" ), QStringLiteral("linux@jrockey.com") );
0427     aboutData.addCredit( i18n("Ajay Guleria"), i18n( "ClearCase support" ), QStringLiteral("ajay_guleria@yahoo.com") );
0428     aboutData.addCredit( i18n("Marek Janukowicz"), i18n( "Ruby support" ), QStringLiteral("child@t17.ds.pwr.wroc.pl") );
0429     aboutData.addCredit( i18n("Robert Moniot"), i18n( "Fortran documentation" ), QStringLiteral("moniot@fordham.edu") );
0430     aboutData.addCredit( i18n("Ka-Ping Yee"), i18n( "Python documentation utility" ), QStringLiteral("ping@lfw.org") );
0431     aboutData.addCredit( i18n("Dimitri van Heesch"), i18n( "Doxygen wizard" ), QStringLiteral("dimitri@stack.nl") );
0432     aboutData.addCredit( i18n("Hugo Varotto"), i18n( "Fileselector component" ), QStringLiteral("hugo@varotto-usa.com") );
0433     aboutData.addCredit( i18n("Matt Newell"), i18n( "Fileselector component" ), QStringLiteral("newellm@proaxis.com") );
0434     aboutData.addCredit( i18n("Daniel Engelschalt"), i18n( "C++ code completion, persistent class store" ), QStringLiteral("daniel.engelschalt@gmx.net") );
0435     aboutData.addCredit( i18n("Stephane Ancelot"), i18n( "Patches" ), QStringLiteral("sancelot@free.fr") );
0436     aboutData.addCredit( i18n("Jens Zurheide"), i18n( "Patches" ), QStringLiteral("jens.zurheide@gmx.de") );
0437     aboutData.addCredit( i18n("Luc Willems"), i18n( "Help with Perl support" ), QStringLiteral("Willems.luc@pandora.be") );
0438     aboutData.addCredit( i18n("Marcel Turino"), i18n( "Documentation index view" ), QStringLiteral("M.Turino@gmx.de") );
0439     aboutData.addCredit( i18n("Yann Hodique"), i18n( "Patches" ), QStringLiteral("Yann.Hodique@lifl.fr") );
0440     aboutData.addCredit( i18n("Tobias Gl\303\244\303\237er") , i18n( "Documentation Finder,  qmake projectmanager patches, usability improvements, bugfixes ... " ), QStringLiteral("tobi.web@gmx.de") );
0441     aboutData.addCredit( i18n("Andreas Koepfle") , i18n( "QMake project manager patches" ), QStringLiteral("koepfle@ti.uni-mannheim.de") );
0442     aboutData.addCredit( i18n("Sascha Cunz") , i18n( "Cleanup and bugfixes for qEditor, AutoMake and much other stuff" ), QStringLiteral("mail@sacu.de") );
0443     aboutData.addCredit( i18n("Zoran Karavla"), i18n( "Artwork for the ruby language" ), QStringLiteral("webmaster@the-error.net"), QStringLiteral("http://the-error.net") );
0444 
0445     KAboutData::setApplicationData(aboutData);
0446     // set icon for shells which do not use desktop file metadata
0447     // but without setting replacing an existing icon with an empty one!
0448     QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("kdevelop"), QApplication::windowIcon()));
0449 
0450     KCrash::initialize();
0451 
0452     Kdelibs4ConfigMigrator migrator(QStringLiteral("kdevelop"));
0453     migrator.setConfigFiles({QStringLiteral("kdeveloprc")});
0454     migrator.setUiFiles({QStringLiteral("kdevelopui.rc")});
0455     migrator.migrate();
0456 
0457     // High DPI support
0458     app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
0459 
0460     qCDebug(APP) << "Startup";
0461 
0462     QCommandLineParser parser;
0463     aboutData.setupCommandLine(&parser);
0464 
0465     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("n"), QStringLiteral("new-session")},
0466                      i18n("Open KDevelop with a new session using the given name."),
0467                      QStringLiteral("name")});
0468     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("s"), QStringLiteral("open-session")},
0469                      i18n("Open KDevelop with the given session.\n"
0470                           "You can pass either hash or the name of the session."),
0471                      QStringLiteral("session")});
0472     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("rm"), QStringLiteral("remove-session")},
0473                      i18n("Delete the given session.\n"
0474                           "You can pass either hash or the name of the session." ),
0475                      QStringLiteral("session")});
0476     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("ps"), QStringLiteral("pick-session")},
0477                      i18n("Shows all available sessions and lets you select one to open.")});
0478     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("pss"), QStringLiteral("pick-session-shell")},
0479                      i18n("List all available sessions on shell and lets you select one to open.")});
0480     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("l"), QStringLiteral("list-sessions")},
0481                      i18n("List available sessions and quit.")});
0482     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("f"), QStringLiteral("fetch")},
0483                      i18n("Open KDevelop and fetch the project from the given <repo url>."),
0484                      QStringLiteral("repo url")});
0485     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("p"), QStringLiteral("project")},
0486                      i18n("Open KDevelop and load the given project. <project> can be either a .kdev4 file or a directory path."),
0487                      QStringLiteral("project")});
0488     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("d"), QStringLiteral("debug")},
0489                      i18n("Start debugging an application in KDevelop with the given debugger.\n"
0490                      "The executable that should be debugged must follow - including arguments.\n"
0491                      "Example: kdevelop --debug gdb myapp --foo bar"), QStringLiteral("debugger")});
0492 
0493     // this is used by the 'kdevelop!' script to retrieve the pid of a KDEVELOP
0494     // instance. When this is called, then we should just print the PID on the
0495     // standard-output. If a session is specified through open-session, then
0496     // we should return the PID of that session. Otherwise, if only a single
0497     // session is running, then we should just return the PID of that session.
0498     // Otherwise, we should print a command-line session-chooser dialog ("--pss"),
0499     // which only shows the running sessions, and the user can pick one.
0500     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("pid")}});
0501 
0502     parser.addPositionalArgument(QStringLiteral("files"),
0503                      i18n( "Files to load, or directories to load as projects" ), QStringLiteral("[FILE[:line[:column]] | DIRECTORY]..."));
0504 
0505     // The session-controller needs to arguments to eventually pass them to newly opened sessions
0506     KDevelop::SessionController::setArguments(argc, argv);
0507 
0508     parser.process(app);
0509     aboutData.processCommandLine(&parser);
0510 
0511     if(parser.isSet(QStringLiteral("list-sessions")))
0512     {
0513         QTextStream qout(stdout);
0514         qout << QLatin1Char('\n') << ki18n("Available sessions (use '-s HASH' or '-s NAME' to open a specific one):").toString() << QLatin1String("\n\n");
0515         qout << QStringLiteral("%1").arg(ki18n("Hash").toString(), -38) << '\t' << ki18n("Name: Opened Projects").toString() << QLatin1Char('\n');
0516         const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos();
0517         for (const KDevelop::SessionInfo& si : availableSessionInfos) {
0518             if ( si.name.isEmpty() && si.projects.isEmpty() ) {
0519                 continue;
0520             }
0521             qout << si.uuid.toString() << '\t' << si.description;
0522 
0523             if(KDevelop::SessionController::isSessionRunning(si.uuid.toString()))
0524                 qout << "     " << i18n("[running]");
0525 
0526             qout << QLatin1Char('\n');
0527         }
0528         return 0;
0529     }
0530 
0531     // Handle extra arguments, which stand for files to open
0532     QVector<UrlInfo> initialFiles;
0533     QVector<UrlInfo> initialDirectories;
0534     const auto files = parser.positionalArguments();
0535     for (const QString& file : files) {
0536         const UrlInfo info(file);
0537         if (info.isDirectory()) {
0538             initialDirectories.append(info);
0539         } else {
0540             initialFiles.append(info);
0541         }
0542     }
0543 
0544     const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos();
0545 
0546     if ((!initialFiles.isEmpty() || !initialDirectories.isEmpty()) && !parser.isSet(QStringLiteral("new-session"))) {
0547 #if KDEVELOP_SINGLE_APP
0548         if (app.isRunning()) {
0549             bool success = app.sendMessage(serializeOpenFilesMessage(initialFiles << initialDirectories));
0550             if (success) {
0551                 return 0;
0552             }
0553         }
0554 #else
0555         qint64 pid = -1;
0556         if (parser.isSet(QStringLiteral("open-session"))) {
0557             const QString session = findSessionId(availableSessionInfos, parser.value(QStringLiteral("open-session")));
0558             if (session.isEmpty()) {
0559                 return 1;
0560             } else if (KDevelop::SessionController::isSessionRunning(session)) {
0561                 pid = findSessionPid(session);
0562             }
0563         } else {
0564             pid = getRunningSessionPid();
0565         }
0566 
0567         if ( pid > 0 ) {
0568             return openFilesInRunningInstance(initialFiles, pid) + openProjectInRunningInstance(initialDirectories, pid);
0569         }
0570         // else there are no running sessions, and the generated list of files will be opened below.
0571 #endif
0572     }
0573 
0574     // if empty, restart kdevelop with last active session, see SessionController::defaultSessionId
0575     QString session;
0576 
0577     uint nRunningSessions = 0;
0578     for (const KDevelop::SessionInfo& si : availableSessionInfos) {
0579         if(KDevelop::SessionController::isSessionRunning(si.uuid.toString()))
0580             ++nRunningSessions;
0581     }
0582 
0583     // also show the picker dialog when a pid shall be retrieved and multiple
0584     // sessions are running.
0585     if(parser.isSet(QStringLiteral("pss")) || (parser.isSet(QStringLiteral("pid")) && !parser.isSet(QStringLiteral("open-session")) && !parser.isSet(QStringLiteral("ps")) && nRunningSessions > 1))
0586     {
0587         SessionInfos candidates;
0588         for (const KDevelop::SessionInfo& si : availableSessionInfos) {
0589             if( (!si.name.isEmpty() || !si.projects.isEmpty() || parser.isSet(QStringLiteral("pid"))) &&
0590                 (!parser.isSet(QStringLiteral("pid")) || KDevelop::SessionController::isSessionRunning(si.uuid.toString())))
0591                 candidates << si;
0592         }
0593 
0594         if(candidates.size() == 0)
0595         {
0596             QTextStream qerr(stderr);
0597             qerr << "no session available" << QLatin1Char('\n');
0598             return 1;
0599         }
0600 
0601         if(candidates.size() == 1 && parser.isSet(QStringLiteral("pid")))
0602         {
0603             session = candidates.constFirst().uuid.toString();
0604         }else{
0605             QTextStream qout(stdout);
0606             for(int i = 0; i < candidates.size(); ++i)
0607                 qout << "[" << i << "]: " << candidates.at(i).description << QLatin1Char('\n');
0608             qout.flush();
0609 
0610             int chosen;
0611             std::cin >> chosen;
0612             if(std::cin.good() && (chosen >= 0 && chosen < candidates.size()))
0613             {
0614                 session = candidates.at(chosen).uuid.toString();
0615             }else{
0616                 QTextStream qerr(stderr);
0617                 qerr << "invalid selection" << QLatin1Char('\n');
0618                 return 1;
0619             }
0620         }
0621     }
0622 
0623     if(parser.isSet(QStringLiteral("ps")))
0624     {
0625         bool onlyRunning = parser.isSet(QStringLiteral("pid"));
0626         session = KDevelop::SessionController::showSessionChooserDialog(i18n("Select the session you would like to use"), onlyRunning);
0627         if(session.isEmpty())
0628             return 1;
0629     }
0630 
0631     if ( parser.isSet(QStringLiteral("debug")) ) {
0632         if ( debugArgs.isEmpty() ) {
0633             QTextStream qerr(stderr);
0634             qerr << QLatin1Char('\n') << i18nc("@info:shell", "Specify the executable you want to debug.") << QLatin1Char('\n');
0635             return 1;
0636         }
0637 
0638         QFileInfo executableFileInfo(debugArgs.first());
0639         if (!executableFileInfo.exists()) {
0640             executableFileInfo = QStandardPaths::findExecutable(debugArgs.first());
0641             if (!executableFileInfo.exists()) {
0642                 QTextStream qerr(stderr);
0643                 qerr << QLatin1Char('\n') << i18nc("@info:shell", "Specified executable does not exist.") << QLatin1Char('\n');
0644                 return 1;
0645             }
0646         }
0647 
0648         debugArgs.first() = executableFileInfo.absoluteFilePath();
0649         debugeeName = i18n("Debug %1", executableFileInfo.fileName());
0650         session = debugeeName;
0651     } else if ( parser.isSet(QStringLiteral("new-session")) )
0652     {
0653         session = parser.value(QStringLiteral("new-session"));
0654         for (const KDevelop::SessionInfo& si : availableSessionInfos) {
0655             if ( session == si.name ) {
0656                 QTextStream qerr(stderr);
0657                 qerr << QLatin1Char('\n') << i18n("A session with the name %1 exists already. Use the -s switch to open it.", session) << QLatin1Char('\n');
0658                 return 1;
0659             }
0660         }
0661         // session doesn't exist, we can create it
0662     } else if ( parser.isSet(QStringLiteral("open-session")) ) {
0663         session = findSessionId(availableSessionInfos, parser.value(QStringLiteral("open-session")));
0664         if (session.isEmpty()) {
0665             return 1;
0666         }
0667     } else if ( parser.isSet(QStringLiteral("remove-session")) )
0668     {
0669         session = parser.value(QStringLiteral("remove-session"));
0670         auto si = findSessionInList(availableSessionInfos, session);
0671         if (!si) {
0672             QTextStream qerr(stderr);
0673             qerr << QLatin1Char('\n') << i18n("No session with the name %1 exists.", session) << QLatin1Char('\n');
0674             return 1;
0675         }
0676 
0677         auto sessionLock = KDevelop::SessionController::tryLockSession(si->uuid.toString());
0678         if (!sessionLock.lock) {
0679             QTextStream qerr(stderr);
0680             qerr << QLatin1Char('\n') << i18n("Could not lock session %1 for deletion.", session) << QLatin1Char('\n');
0681             return 1;
0682         }
0683         KDevelop::SessionController::deleteSessionFromDisk(sessionLock.lock);
0684         QTextStream qout(stdout);
0685         qout << QLatin1Char('\n') << i18n("Session with name %1 was successfully removed.", session) << QLatin1Char('\n');
0686         return 0;
0687     }
0688 
0689     if(parser.isSet(QStringLiteral("pid"))) {
0690         if (session.isEmpty())
0691         {   // just pick the first running session
0692             for (const KDevelop::SessionInfo& si : availableSessionInfos) {
0693                 if(KDevelop::SessionController::isSessionRunning(si.uuid.toString()))
0694                     session = si.uuid.toString();
0695             }
0696         }
0697         const KDevelop::SessionInfo* sessionData = findSessionInList(availableSessionInfos, session);
0698 
0699         if( !sessionData ) {
0700             qCCritical(APP) << "session not given or does not exist";
0701             return 5;
0702         }
0703 
0704         const auto pid = findSessionPid(sessionData->uuid.toString());
0705         if (pid > 0) {
0706             // Print the PID and we're ready
0707             std::cout << pid << std::endl;
0708             return 0;
0709         } else {
0710             qCCritical(APP) << sessionData->uuid.toString() << sessionData->name << "is not running";
0711             return 5;
0712         }
0713     }
0714 
0715     if (parser.isSet(QStringLiteral("project"))) {
0716         const auto project = parser.value(QStringLiteral("project"));
0717         QFileInfo info(project);
0718         QUrl projectUrl;
0719         if (info.suffix() == QLatin1String("kdev4")) {
0720             projectUrl = QUrl::fromLocalFile(info.absoluteFilePath());
0721         } else if (info.isDir()) {
0722             QDir dir(info.absoluteFilePath());
0723             const auto potentialProjectFiles = dir.entryList({QStringLiteral("*.kdev4")}, QDir::Files, QDir::Name);
0724             qCDebug(APP) << "Found these potential project files:" << potentialProjectFiles;
0725             if (!potentialProjectFiles.isEmpty()) {
0726                 projectUrl = QUrl::fromLocalFile(dir.absoluteFilePath(potentialProjectFiles.value(0)));
0727             }
0728         } else {
0729             QTextStream qerr(stderr);
0730             qerr << "Invalid project: " << project << " - should be either a path to a .kdev4 file or a directory containing a .kdev4 file";
0731             return 1;
0732         }
0733 
0734         qCDebug(APP) << "Attempting to find a suitable session for project" << projectUrl;
0735         const auto sessionInfos = findSessionsWithProject(availableSessionInfos, projectUrl);
0736         qCDebug(APP) << "Found matching sessions:" << sessionInfos.size();
0737         if (!sessionInfos.isEmpty()) {
0738             // TODO: If there's more than one match: Allow the user to select which session to open?
0739             qCDebug(APP) << "Attempting to open session:" << sessionInfos.at(0).name;
0740             session = sessionInfos.at(0).uuid.toString();
0741         }
0742     }
0743 
0744     KDevIDEExtension::init();
0745 
0746     qCDebug(APP) << "Attempting to initialize session:" << session;
0747     if(!Core::initialize(Core::Default, session))
0748         return 5;
0749 
0750     // register a DBUS service for this process, so that we can open files in it from other invocations
0751     QDBusConnection::sessionBus().registerService(QStringLiteral("org.kdevelop.kdevelop-%1").arg(app.applicationPid()));
0752 
0753     Core* core = Core::self();
0754     if (!QProcessEnvironment::systemEnvironment().contains(QStringLiteral("KDEV_DISABLE_WELCOMEPAGE"))) {
0755         core->pluginController()->loadPlugin(QStringLiteral("KDevWelcomePage"));
0756     }
0757 
0758     const auto fetchUrlStrings = parser.values(QStringLiteral("fetch"));
0759     for (const auto& fetchUrlString : fetchUrlStrings) {
0760         core->projectControllerInternal()->fetchProjectFromUrl(QUrl::fromUserInput(fetchUrlString));
0761     }
0762 
0763     const QString debugStr = QStringLiteral("debug");
0764     if ( parser.isSet(debugStr) ) {
0765         Q_ASSERT( !debugeeName.isEmpty() );
0766         QString launchName = debugeeName;
0767 
0768         KDevelop::LaunchConfiguration* launch = nullptr;
0769         qCDebug(APP) << launchName;
0770         const auto launchconfigurations = core->runControllerInternal()->launchConfigurationsInternal();
0771         for (KDevelop::LaunchConfiguration* l : launchconfigurations) {
0772             qCDebug(APP) << l->name();
0773             if (l->name() == launchName) {
0774                 launch = l;
0775             }
0776         }
0777 
0778         KDevelop::LaunchConfigurationType *type = nullptr;
0779         const auto launchConfigurationTypes = core->runController()->launchConfigurationTypes();
0780         for (KDevelop::LaunchConfigurationType* t : launchConfigurationTypes) {
0781             qCDebug(APP) << t->id();
0782             if (t->id() == QLatin1String("Native Application")) {
0783                 type = t;
0784                 break;
0785             }
0786         }
0787         if (!type) {
0788             QTextStream qerr(stderr);
0789             qerr << QLatin1Char('\n') << i18n("Cannot find native launch configuration type") << QLatin1Char('\n');
0790             return 1;
0791         }
0792 
0793         if (launch && launch->type()->id() != QLatin1String("Native Application")) launch = nullptr;
0794         if (launch && launch->launcherForMode(debugStr) != parser.value(debugStr)) launch = nullptr;
0795         if (!launch) {
0796             qCDebug(APP) << launchName << "not found, creating a new one";
0797             QPair<QString,QString> launcher;
0798             launcher.first = debugStr;
0799             const auto typeLaunchers = type->launchers();
0800             for (KDevelop::ILauncher* l : typeLaunchers) {
0801                 if (l->id() == parser.value(debugStr)) {
0802                     if (l->supportedModes().contains(debugStr)) {
0803                         launcher.second = l->id();
0804                     }
0805                 }
0806             }
0807             if (launcher.second.isEmpty()) {
0808                 QTextStream qerr(stderr);
0809                 qerr << QLatin1Char('\n') << i18n("Cannot find launcher %1", parser.value(debugStr)) << QLatin1Char('\n');
0810                 return 1;
0811             }
0812             KDevelop::ILaunchConfiguration* ilaunch = core->runController()->createLaunchConfiguration(type, launcher, nullptr, launchName);
0813             launch = static_cast<KDevelop::LaunchConfiguration*>(ilaunch);
0814         }
0815 
0816         type->configureLaunchFromCmdLineArguments(launch->config(), debugArgs);
0817         launch->config().writeEntry("Break on Start", true);
0818         core->runControllerInternal()->setDefaultLaunch(launch);
0819 
0820         core->runControllerInternal()->execute(debugStr, launch);
0821     } else {
0822         openFiles(initialFiles);
0823 
0824         for(const auto& urlinfo: qAsConst(initialDirectories))
0825             core->projectController()->openProjectForUrl(urlinfo.url);
0826     }
0827 
0828 #if KDEVELOP_SINGLE_APP
0829     // Set up remote arguments.
0830     QObject::connect(&app, &SharedTools::QtSingleApplication::messageReceived,
0831                      &app, &KDevelopApplication::remoteArguments);
0832 
0833     QObject::connect(&app, &SharedTools::QtSingleApplication::fileOpenRequest,
0834                      &app, &KDevelopApplication::fileOpenRequested);
0835 #endif
0836 
0837 
0838     qCDebug(APP) << "Done startup" << "- took:" << timer.elapsed() << "ms";
0839     timer.invalidate();
0840 
0841     return app.exec();
0842 }
0843 
0844 #include "main.moc"