1 /*
2     SPDX-FileCopyrightText: 2003-2009 Alexander Dymo <adymo@kdevelop.org>
3     SPDX-FileCopyrightText: 2007 Ralf Habacker <Ralf.Habacker@freenet.de>
4     SPDX-FileCopyrightText: 2006-2007 Matt Rogers <mattr@kde.org>
5     SPDX-FileCopyrightText: 2006-2007 Hamish Rodda <rodda@kde.org>
6     SPDX-FileCopyrightText: 2005-2007 Adam Treat <treat@kde.org>
7     SPDX-FileCopyrightText: 2003-2007 Jens Dagerbo <jens.dagerbo@swipnet.se>
8     SPDX-FileCopyrightText: 2001-2002 Bernd Gehrmann <bernd@mail.berlios.de>
9     SPDX-FileCopyrightText: 2001-2002 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
10     SPDX-FileCopyrightText: 2003 Roberto Raggi <roberto@kdevelop.org>
11     SPDX-FileCopyrightText: 2010 Niko Sams <niko.sams@gmail.com>
12     SPDX-FileCopyrightText: 2015 Kevin Funk <kfunk@kde.org>
13 
14     SPDX-License-Identifier: LGPL-2.0-or-later
15 */
16 
17 #include "config-kdevelop.h"
18 #include "kdevelop_version.h"
19 
20 #include "urlinfo.h"
21 
22 #include <KLocalizedString>
23 #include <Kdelibs4ConfigMigrator>
24 #include <KAboutData>
25 #include <KCrash>
26 
27 #include <QApplication>
28 #include <QElapsedTimer>
29 #include <QCommandLineParser>
30 #include <QCommandLineOption>
31 #include <QFileInfo>
32 #include <QProcessEnvironment>
33 #include <QSessionManager>
34 #include <QTextStream>
35 #include <QDBusInterface>
36 #include <QDBusReply>
37 
38 #include <QQuickWindow>
39 
40 #include <shell/core.h>
41 #include <shell/mainwindow.h>
42 #include <shell/projectcontroller.h>
43 #include <shell/documentcontroller.h>
44 #include <shell/plugincontroller.h>
45 #include <shell/sessioncontroller.h>
46 #include <shell/runcontroller.h>
47 #include <shell/launchconfiguration.h>
48 #include <shell/session.h>
49 #include <interfaces/ilauncher.h>
50 #include <interfaces/iproject.h>
51 #include <interfaces/launchconfigurationtype.h>
52 #include <util/path.h>
53 #include <debug.h>
54 
55 #include "kdevideextension.h"
56 #if KDEVELOP_SINGLE_APP
57 #include "qtsingleapplication.h"
58 #endif
59 
60 #include <iostream>
61 
62 #ifdef Q_OS_MAC
63 #include <CoreFoundation/CoreFoundation.h>
64 #endif
65 
66 using namespace KDevelop;
67 
68 namespace {
69 
70 #if KDEVELOP_SINGLE_APP
serializeOpenFilesMessage(const QVector<UrlInfo> & infos)71 QString serializeOpenFilesMessage(const QVector<UrlInfo> &infos)
72 {
73     QByteArray message;
74     QDataStream stream(&message, QIODevice::WriteOnly);
75     stream << QByteArrayLiteral("open");
76     stream << infos;
77     return QString::fromLatin1(message.toHex());
78 }
79 #endif
80 
openFiles(const QVector<UrlInfo> & infos)81 void openFiles(const QVector<UrlInfo>& infos)
82 {
83     for (const UrlInfo& info : infos) {
84         if (!ICore::self()->documentController()->openDocument(info.url, info.cursor)) {
85             qCWarning(APP) << i18n("Could not open %1", info.url.toDisplayString(QUrl::PreferLocalFile));
86         }
87     }
88 }
89 
90 }
91 
92 class KDevelopApplication:
93 #if KDEVELOP_SINGLE_APP
94     public SharedTools::QtSingleApplication
95 #else
96     public QApplication
97 #endif
98 {
99     Q_OBJECT
100 public:
KDevelopApplication(int & argc,char ** argv,bool GUIenabled=true)101     explicit KDevelopApplication(int &argc, char **argv, bool GUIenabled = true)
102 #if KDEVELOP_SINGLE_APP
103         : SharedTools::QtSingleApplication(QStringLiteral("KDevelop"), argc, argv)
104 #else
105         : QApplication(argc, argv, GUIenabled)
106 #endif
107         {
108             Q_UNUSED(GUIenabled);
109             connect(this, &QGuiApplication::saveStateRequest, this, &KDevelopApplication::saveState);
110         }
111 
112 #if KDEVELOP_SINGLE_APP
113 public Q_SLOTS:
remoteArguments(const QString & message,QObject * socket)114     void remoteArguments(const QString &message, QObject *socket)
115     {
116         Q_UNUSED(socket);
117 
118         QByteArray ba = QByteArray::fromHex(message.toLatin1());
119         QDataStream stream(ba);
120         QByteArray command;
121         stream >> command;
122 
123         qCDebug(APP) << "Received remote command: " << command;
124 
125         if (command == "open") {
126             QVector<UrlInfo> infos;
127             stream >> infos;
128 
129             QVector<UrlInfo> files, directories;
130             for (const auto& info : infos)
131                 if (info.isDirectory())
132                     directories << info;
133                 else
134                     files << info;
135 
136             openFiles(files);
137             for(const auto &urlinfo : directories)
138                 ICore::self()->projectController()->openProjectForUrl(urlinfo.url);
139         } else {
140             qCWarning(APP) << "Unknown remote command: " << command;
141         }
142     }
143 
fileOpenRequested(const QString & file)144     void fileOpenRequested(const QString &file)
145     {
146         openFiles({UrlInfo(file)});
147     }
148 #endif
149 
150 private Q_SLOTS:
saveState(QSessionManager & sm)151     void saveState( QSessionManager& sm ) {
152         if (KDevelop::Core::self() && KDevelop::Core::self()->sessionController()) {
153             const auto activeSession = KDevelop::Core::self()->sessionController()->activeSession();
154             if (!activeSession) {
155                 qCWarning(APP) << "No active session, can't save state";
156                 return;
157             }
158 
159             const QString x11SessionId = sm.sessionId() + QLatin1Char('_') + sm.sessionKey();
160             QString kdevelopSessionId = activeSession->id().toString();
161             sm.setRestartCommand({
162                 QCoreApplication::applicationFilePath(),
163                 QStringLiteral("-session"),
164                 x11SessionId,
165                 QStringLiteral("-s"),
166                 kdevelopSessionId
167             });
168         }
169     }
170 };
171 
172 /// Tries to find a session identified by @p data in @p sessions.
173 /// The @p data may be either a session's name or a string-representation of its UUID.
174 /// @return pointer to the session or NULL if nothing appropriate has been found
findSessionInList(const SessionInfos & sessions,const QString & data)175 static const KDevelop::SessionInfo* findSessionInList( const SessionInfos& sessions, const QString& data )
176 {
177     // We won't search a session without input data, since that could lead to false-positives
178     // with unnamed sessions
179     if( data.isEmpty() )
180         return nullptr;
181 
182     for( auto it = sessions.constBegin(); it != sessions.constEnd(); ++it ) {
183         if ( ( it->name == data ) || ( it->uuid.toString() == data ) ) {
184             const KDevelop::SessionInfo& sessionRef = *it;
185             return &sessionRef;
186         }
187     }
188     return nullptr;
189 }
190 
191 /// Tries to find sessions containing project @p projectUrl in @p sessions.
findSessionsWithProject(const SessionInfos & sessions,const QUrl & projectUrl)192 static const KDevelop::SessionInfos findSessionsWithProject(const SessionInfos& sessions, const QUrl& projectUrl)
193 {
194     if (!projectUrl.isValid())
195         return {};
196 
197     KDevelop::SessionInfos infos;
198     for (auto& session : sessions) {
199         if (session.projects.contains(projectUrl)) {
200             infos << session;
201         }
202     }
203     return infos;
204 }
205 
206 /// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid
207 /// Returns the exit status
openFilesInRunningInstance(const QVector<UrlInfo> & files,qint64 pid)208 static int openFilesInRunningInstance(const QVector<UrlInfo>& files, qint64 pid)
209 {
210     const QString service = QStringLiteral("org.kdevelop.kdevelop-%1").arg(pid);
211     QDBusInterface iface(service, QStringLiteral("/org/kdevelop/DocumentController"), QStringLiteral("org.kdevelop.DocumentController"));
212 
213     QStringList urls;
214     bool errors_occurred = false;
215     for (const UrlInfo& file : files) {
216         QDBusReply<bool> result = iface.call(QStringLiteral("openDocumentSimple"), file.url.toString(), file.cursor.line(), file.cursor.column());
217         if ( ! result.value() ) {
218             QTextStream err(stderr);
219             err << i18n("Could not open file '%1'.", file.url.toDisplayString(QUrl::PreferLocalFile)) << "\n";
220             errors_occurred = true;
221         }
222     }
223     // make the window visible
224     QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"),
225                                                                QStringLiteral("ensureVisible") );
226     QDBusConnection::sessionBus().asyncCall( makeVisible );
227     return errors_occurred;
228 }
229 
230 /// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid
231 /// Returns the exit status
openProjectInRunningInstance(const QVector<UrlInfo> & paths,qint64 pid)232 static int openProjectInRunningInstance(const QVector<UrlInfo>& paths, qint64 pid)
233 {
234     const QString service = QStringLiteral("org.kdevelop.kdevelop-%1").arg(pid);
235     QDBusInterface iface(service, QStringLiteral("/org/kdevelop/ProjectController"), QStringLiteral("org.kdevelop.ProjectController"));
236     int errors = 0;
237 
238     for (const UrlInfo& path : paths) {
239         QDBusReply<void> result = iface.call(QStringLiteral("openProjectForUrl"), path.url.toString());
240         if ( !result.isValid() ) {
241             QTextStream err(stderr);
242             err << i18n("Could not open project '%1': %2", path.url.toDisplayString(QUrl::PreferLocalFile), result.error().message()) << "\n";
243             ++errors;
244         }
245     }
246     // make the window visible
247     QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"),
248                                                                QStringLiteral("ensureVisible") );
249     QDBusConnection::sessionBus().asyncCall( makeVisible );
250     return errors;
251 }
252 
253 /// Gets the PID of a running KDevelop instance, eventually asking the user if there is more than one.
254 /// Returns -1 in case there are no running sessions.
getRunningSessionPid()255 static qint64 getRunningSessionPid()
256 {
257     SessionInfos candidates;
258     const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos();
259     for (const KDevelop::SessionInfo& si : availableSessionInfos) {
260         if( KDevelop::SessionController::isSessionRunning(si.uuid.toString()) ) {
261             candidates << si;
262         }
263     }
264     if ( candidates.isEmpty() ) {
265         return -1;
266     }
267 
268     QString sessionUuid;
269     if ( candidates.size() == 1 ) {
270         sessionUuid = candidates.first().uuid.toString();
271     }
272     else {
273         const QString title = i18n("Select the session to open the document in");
274         sessionUuid = KDevelop::SessionController::showSessionChooserDialog(title, true);
275     }
276     return KDevelop::SessionController::sessionRunInfo(sessionUuid).holderPid;
277 }
278 
findSessionId(const SessionInfos & availableSessionInfos,const QString & session)279 static QString findSessionId(const SessionInfos& availableSessionInfos, const QString& session)
280 {
281     //If there is a session and a project with the same name, always open the session
282     //regardless of the order encountered
283     QString projectAsSession;
284     for (const KDevelop::SessionInfo& si : availableSessionInfos) {
285         if ( session == si.name || session == si.uuid.toString() ) {
286             return si.uuid.toString();
287         } else if (projectAsSession.isEmpty()) {
288             for (const QUrl& k : si.projects) {
289                 QString fn(k.fileName());
290                 fn = fn.left(fn.indexOf(QLatin1Char('.')));
291                 if ( session == fn ) {
292                     projectAsSession = si.uuid.toString();
293                 }
294             }
295         }
296     }
297 
298     if (projectAsSession.isEmpty())  {
299         QTextStream qerr(stderr);
300         qerr << QLatin1Char('\n') << i18n("Cannot open unknown session %1. See `--list-sessions` switch for available sessions or use `-n` to create a new one.",
301                              session) << QLatin1Char('\n');
302     }
303     return projectAsSession;
304 }
305 
findSessionPid(const QString & sessionId)306 static qint64 findSessionPid(const QString &sessionId)
307 {
308     KDevelop::SessionRunInfo sessionInfo = KDevelop::SessionController::sessionRunInfo( sessionId );
309     return sessionInfo.holderPid;
310 }
311 
main(int argc,char * argv[])312 int main( int argc, char *argv[] )
313 {
314     QElapsedTimer timer;
315     timer.start();
316 
317     // If possible, use the Software backend for QQuickWidget (currently used in the
318     // welcome page plugin). This means we don't need OpenGL at all, avoiding issues
319     // like https://bugs.kde.org/show_bug.cgi?id=386527.
320     QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software);
321 
322     // TODO: Maybe generalize, add KDEVELOP_STANDALONE build option
323 #if defined(Q_OS_WIN) || defined(Q_OS_MAC)
324     qputenv("KDE_FORK_SLAVES", "1"); // KIO slaves will be forked off instead of being started via DBus
325 #endif
326 
327     // Useful for valgrind runs, just `export KDEV_DISABLE_JIT=1`
328     if (qEnvironmentVariableIsSet("KDEV_DISABLE_JIT")) {
329         qputenv("KDEV_DISABLE_WELCOMEPAGE", "1");
330         qputenv("QT_ENABLE_REGEXP_JIT", "0");
331     }
332 
333     QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
334     QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
335 
336 #ifdef Q_OS_MAC
337     CFBundleRef mainBundle = CFBundleGetMainBundle();
338     if (mainBundle) {
339         // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist,
340         // for regular executables it is obtained in another way.
341         CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle);
342         if (infoDict) {
343             // Try to prevent App Nap on OS X. This can be tricky in practice, at least in 10.9 .
344             CFDictionarySetValue(infoDict, CFSTR("NSAppSleepDisabled"), kCFBooleanTrue);
345             CFDictionarySetValue(infoDict, CFSTR("NSSupportsAutomaticTermination"), kCFBooleanFalse);
346         }
347     }
348 #endif
349 
350     //we can't use KCmdLineArgs as it doesn't allow arguments for the debugee
351     //so lookup the --debug switch and eat everything behind by decrementing argc
352     //debugArgs is filled with args after --debug <debuger>
353     QStringList debugArgs;
354     QString debugeeName;
355     {
356         bool debugFound = false;
357         int c = argc;
358         for (int i=0; i < c; ++i) {
359             if (debugFound) {
360                 debugArgs << QString::fromUtf8(argv[i]);
361             } else if ((qstrcmp(argv[i], "--debug") == 0) || (qstrcmp(argv[i], "-d") == 0)) {
362                 if (argc > (i + 1)) {
363                     i++;
364                 }
365                 argc = i + 1;
366                 debugFound = true;
367             } else if (QByteArray(argv[i]).startsWith("--debug=")) {
368                 argc = i + 1;
369                 debugFound = true;
370             }
371         }
372     }
373 
374     KDevelopApplication app(argc, argv);
375     // Prevent SIGPIPE, then "ICE default IO error handler doing an exit(), pid = <PID>, errno = 32"
376     // crash when the first event loop starts at least 60 seconds after KDevelop launch. This can
377     // happen during a Debug Launch of KDevelop from KDevelop, especially if a breakpoint is hit
378     // before any event loop is entered.
379     QCoreApplication::processEvents();
380 
381     KLocalizedString::setApplicationDomain("kdevelop");
382 
383     KAboutData aboutData(QStringLiteral("kdevelop"), i18n("KDevelop"),
384                          QStringLiteral(KDEVELOP_VERSION_STRING " (" RELEASE_SERVICE_VERSION_STRING ")"),
385                          i18n("The KDevelop Integrated Development Environment"), KAboutLicense::GPL,
386                          i18n("Copyright 1999-%1, The KDevelop developers", QStringLiteral("2021")), QString(),
387                          QStringLiteral("https://www.kdevelop.org/"));
388     aboutData.setDesktopFileName(QStringLiteral("org.kde.kdevelop"));
389     aboutData.addAuthor( i18n("Kevin Funk"), i18n( "Co-maintainer, C++/Clang, QA, Windows Support, Performance, Website" ), QStringLiteral("kfunk@kde.org") );
390     aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Co-maintainer, AppImage, Python Support, User Interface improvements" ), QStringLiteral("svenbrauch@gmail.com") );
391     aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "CMake Support, Run Support, Kross Support" ), QStringLiteral("aleixpol@kde.org") );
392     aboutData.addAuthor( i18n("Milian Wolff"), i18n( "C++/Clang, Generic manager, Webdevelopment Plugins, Snippets, Performance" ), QStringLiteral("mail@milianw.de") );
393     aboutData.addAuthor( i18n("Olivier JG"), i18n( "C++/Clang, DUChain, Bug Fixes" ), QStringLiteral("olivier.jg@gmail.com") );
394     aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), QStringLiteral("apaku@gmx.de") );
395     aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), QStringLiteral("adymo@kdevelop.org") );
396     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") );
397     aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), QStringLiteral("ghost@cs.msu.su") );
398     aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), QStringLiteral("rodda@kde.org") );
399     aboutData.addAuthor( i18n("Amilcar do Carmo Lucas"), i18n( "Website admin, API documentation, Doxygen and autoproject patches" ), QStringLiteral("amilcar@kdevelop.org") );
400     aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), QStringLiteral("niko.sams@gmail.com") );
401     aboutData.addAuthor( i18n("Friedrich W. H. Kossebau"), QString(), QStringLiteral("kossebau@kde.org") );
402 
403     aboutData.addCredit( i18n("Matt Rogers"), QString(), QStringLiteral("mattr@kde.org"));
404     aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), QStringLiteral("cedric.pasteur@free.fr") );
405     aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), QStringLiteral("powerfox@kde.ru") );
406     // QTest integration is separate in playground currently.
407     //aboutData.addCredit( i18n("Manuel Breugelmanns"), i18n( "Veritas, QTest integration"), "mbr.nxi@gmail.com" );
408     aboutData.addCredit( i18n("Robert Gruber") , i18n( "SnippetPart, debugger and usability patches" ), QStringLiteral("rgruber@users.sourceforge.net") );
409     aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), QStringLiteral("dukjuahn@gmail.com") );
410     aboutData.addCredit( i18n("Harald Fernengel"), i18n( "Ported to Qt 3, patches, valgrind, diff and perforce support" ), QStringLiteral("harry@kdevelop.org") );
411     aboutData.addCredit( i18n("Roberto Raggi"), i18n( "C++ parser" ), QStringLiteral("roberto@kdevelop.org") );
412     aboutData.addCredit( i18n("The KWrite authors"), i18n( "Kate editor component" ), QStringLiteral("kwrite-devel@kde.org") );
413     aboutData.addCredit( i18n("Nokia Corporation/Qt Software"), i18n( "Designer code" ), QStringLiteral("qt-info@nokia.com") );
414 
415     aboutData.addCredit( i18n("Contributors to older versions:"), QString(), QString() );
416     aboutData.addCredit( i18n("Bernd Gehrmann"), i18n( "Initial idea, basic architecture, much initial source code" ), QStringLiteral("bernd@kdevelop.org") );
417     aboutData.addCredit( i18n("Caleb Tennis"), i18n( "KTabBar, bugfixes" ), QStringLiteral("caleb@aei-tech.com") );
418     aboutData.addCredit( i18n("Richard Dale"), i18n( "Java & Objective C support" ), QStringLiteral("Richard_Dale@tipitina.demon.co.uk") );
419     aboutData.addCredit( i18n("John Birch"), i18n( "Debugger frontend" ), QStringLiteral("jbb@kdevelop.org") );
420     aboutData.addCredit( i18n("Sandy Meier"), i18n( "PHP support, context menu stuff" ), QStringLiteral("smeier@kdevelop.org") );
421     aboutData.addCredit( i18n("Kurt Granroth"), i18n( "KDE application templates" ), QStringLiteral("kurth@granroth.org") );
422     aboutData.addCredit( i18n("Ian Reinhart Geiser"), i18n( "Dist part, bash support, application templates" ), QStringLiteral("geiseri@yahoo.com") );
423     aboutData.addCredit( i18n("Matthias Hoelzer-Kluepfel"), i18n( "Several components, htdig indexing" ), QStringLiteral("hoelzer@kde.org") );
424     aboutData.addCredit( i18n("Victor Roeder"), i18n( "Help with Automake manager and persistent class store" ), QStringLiteral("victor_roeder@gmx.de") );
425     aboutData.addCredit( i18n("Simon Hausmann"), i18n( "Help with KParts infrastructure" ), QStringLiteral("hausmann@kde.org") );
426     aboutData.addCredit( i18n("Oliver Kellogg"), i18n( "Ada support" ), QStringLiteral("okellogg@users.sourceforge.net") );
427     aboutData.addCredit( i18n("Jakob Simon-Gaarde"), i18n( "QMake projectmanager" ), QStringLiteral("jsgaarde@tdcspace.dk") );
428     aboutData.addCredit( i18n("Falk Brettschneider"), i18n( "MDI modes, QEditor, bugfixes" ), QStringLiteral("falkbr@kdevelop.org") );
429     aboutData.addCredit( i18n("Mario Scalas"), i18n( "PartExplorer, redesign of CvsPart, patches, bugs(fixes)" ), QStringLiteral("mario.scalas@libero.it") );
430     aboutData.addCredit( i18n("Jens Dagerbo"), i18n( "Replace, Bookmarks, FileList and CTags2 plugins. Overall improvements and patches" ), QStringLiteral("jens.dagerbo@swipnet.se") );
431     aboutData.addCredit( i18n("Julian Rockey"), i18n( "Filecreate part and other bits and patches" ), QStringLiteral("linux@jrockey.com") );
432     aboutData.addCredit( i18n("Ajay Guleria"), i18n( "ClearCase support" ), QStringLiteral("ajay_guleria@yahoo.com") );
433     aboutData.addCredit( i18n("Marek Janukowicz"), i18n( "Ruby support" ), QStringLiteral("child@t17.ds.pwr.wroc.pl") );
434     aboutData.addCredit( i18n("Robert Moniot"), i18n( "Fortran documentation" ), QStringLiteral("moniot@fordham.edu") );
435     aboutData.addCredit( i18n("Ka-Ping Yee"), i18n( "Python documentation utility" ), QStringLiteral("ping@lfw.org") );
436     aboutData.addCredit( i18n("Dimitri van Heesch"), i18n( "Doxygen wizard" ), QStringLiteral("dimitri@stack.nl") );
437     aboutData.addCredit( i18n("Hugo Varotto"), i18n( "Fileselector component" ), QStringLiteral("hugo@varotto-usa.com") );
438     aboutData.addCredit( i18n("Matt Newell"), i18n( "Fileselector component" ), QStringLiteral("newellm@proaxis.com") );
439     aboutData.addCredit( i18n("Daniel Engelschalt"), i18n( "C++ code completion, persistent class store" ), QStringLiteral("daniel.engelschalt@gmx.net") );
440     aboutData.addCredit( i18n("Stephane Ancelot"), i18n( "Patches" ), QStringLiteral("sancelot@free.fr") );
441     aboutData.addCredit( i18n("Jens Zurheide"), i18n( "Patches" ), QStringLiteral("jens.zurheide@gmx.de") );
442     aboutData.addCredit( i18n("Luc Willems"), i18n( "Help with Perl support" ), QStringLiteral("Willems.luc@pandora.be") );
443     aboutData.addCredit( i18n("Marcel Turino"), i18n( "Documentation index view" ), QStringLiteral("M.Turino@gmx.de") );
444     aboutData.addCredit( i18n("Yann Hodique"), i18n( "Patches" ), QStringLiteral("Yann.Hodique@lifl.fr") );
445     aboutData.addCredit( i18n("Tobias Gl\303\244\303\237er") , i18n( "Documentation Finder,  qmake projectmanager patches, usability improvements, bugfixes ... " ), QStringLiteral("tobi.web@gmx.de") );
446     aboutData.addCredit( i18n("Andreas Koepfle") , i18n( "QMake project manager patches" ), QStringLiteral("koepfle@ti.uni-mannheim.de") );
447     aboutData.addCredit( i18n("Sascha Cunz") , i18n( "Cleanup and bugfixes for qEditor, AutoMake and much other stuff" ), QStringLiteral("mail@sacu.de") );
448     aboutData.addCredit( i18n("Zoran Karavla"), i18n( "Artwork for the ruby language" ), QStringLiteral("webmaster@the-error.net"), QStringLiteral("http://the-error.net") );
449 
450     KAboutData::setApplicationData(aboutData);
451     // set icon for shells which do not use desktop file metadata
452     // but without setting replacing an existing icon with an empty one!
453     QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("kdevelop"), QApplication::windowIcon()));
454 
455     KCrash::initialize();
456 
457     Kdelibs4ConfigMigrator migrator(QStringLiteral("kdevelop"));
458     migrator.setConfigFiles({QStringLiteral("kdeveloprc")});
459     migrator.setUiFiles({QStringLiteral("kdevelopui.rc")});
460     migrator.migrate();
461 
462     // High DPI support
463     app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
464 
465     qCDebug(APP) << "Startup";
466 
467     QCommandLineParser parser;
468     aboutData.setupCommandLine(&parser);
469 
470     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("n"), QStringLiteral("new-session")},
471                      i18n("Open KDevelop with a new session using the given name."),
472                      QStringLiteral("name")});
473     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("s"), QStringLiteral("open-session")},
474                      i18n("Open KDevelop with the given session.\n"
475                           "You can pass either hash or the name of the session."),
476                      QStringLiteral("session")});
477     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("rm"), QStringLiteral("remove-session")},
478                      i18n("Delete the given session.\n"
479                           "You can pass either hash or the name of the session." ),
480                      QStringLiteral("session")});
481     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("ps"), QStringLiteral("pick-session")},
482                      i18n("Shows all available sessions and lets you select one to open.")});
483     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("pss"), QStringLiteral("pick-session-shell")},
484                      i18n("List all available sessions on shell and lets you select one to open.")});
485     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("l"), QStringLiteral("list-sessions")},
486                      i18n("List available sessions and quit.")});
487     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("f"), QStringLiteral("fetch")},
488                      i18n("Open KDevelop and fetch the project from the given <repo url>."),
489                      QStringLiteral("repo url")});
490     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("p"), QStringLiteral("project")},
491                      i18n("Open KDevelop and load the given project. <project> can be either a .kdev4 file or a directory path."),
492                      QStringLiteral("project")});
493     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("d"), QStringLiteral("debug")},
494                      i18n("Start debugging an application in KDevelop with the given debugger.\n"
495                      "The executable that should be debugged must follow - including arguments.\n"
496                      "Example: kdevelop --debug gdb myapp --foo bar"), QStringLiteral("debugger")});
497 
498     // this is used by the 'kdevelop!' script to retrieve the pid of a KDEVELOP
499     // instance. When this is called, then we should just print the PID on the
500     // standard-output. If a session is specified through open-session, then
501     // we should return the PID of that session. Otherwise, if only a single
502     // session is running, then we should just return the PID of that session.
503     // Otherwise, we should print a command-line session-chooser dialog ("--pss"),
504     // which only shows the running sessions, and the user can pick one.
505     parser.addOption(QCommandLineOption{QStringList{QStringLiteral("pid")}});
506 
507     parser.addPositionalArgument(QStringLiteral("files"),
508                      i18n( "Files to load, or directories to load as projects" ), QStringLiteral("[FILE[:line[:column]] | DIRECTORY]..."));
509 
510     // The session-controller needs to arguments to eventually pass them to newly opened sessions
511     KDevelop::SessionController::setArguments(argc, argv);
512 
513     parser.process(app);
514     aboutData.processCommandLine(&parser);
515 
516     if(parser.isSet(QStringLiteral("list-sessions")))
517     {
518         QTextStream qout(stdout);
519         qout << QLatin1Char('\n') << ki18n("Available sessions (use '-s HASH' or '-s NAME' to open a specific one):").toString() << QLatin1String("\n\n");
520         qout << QStringLiteral("%1").arg(ki18n("Hash").toString(), -38) << '\t' << ki18n("Name: Opened Projects").toString() << QLatin1Char('\n');
521         const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos();
522         for (const KDevelop::SessionInfo& si : availableSessionInfos) {
523             if ( si.name.isEmpty() && si.projects.isEmpty() ) {
524                 continue;
525             }
526             qout << si.uuid.toString() << '\t' << si.description;
527 
528             if(KDevelop::SessionController::isSessionRunning(si.uuid.toString()))
529                 qout << "     " << i18n("[running]");
530 
531             qout << QLatin1Char('\n');
532         }
533         return 0;
534     }
535 
536     // Handle extra arguments, which stand for files to open
537     QVector<UrlInfo> initialFiles;
538     QVector<UrlInfo> initialDirectories;
539     const auto files = parser.positionalArguments();
540     for (const QString& file : files) {
541         const UrlInfo info(file);
542         if (info.isDirectory()) {
543             initialDirectories.append(info);
544         } else {
545             initialFiles.append(info);
546         }
547     }
548 
549     const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos();
550 
551     if ((!initialFiles.isEmpty() || !initialDirectories.isEmpty()) && !parser.isSet(QStringLiteral("new-session"))) {
552 #if KDEVELOP_SINGLE_APP
553         if (app.isRunning()) {
554             bool success = app.sendMessage(serializeOpenFilesMessage(initialFiles << initialDirectories));
555             if (success) {
556                 return 0;
557             }
558         }
559 #else
560         qint64 pid = -1;
561         if (parser.isSet(QStringLiteral("open-session"))) {
562             const QString session = findSessionId(availableSessionInfos, parser.value(QStringLiteral("open-session")));
563             if (session.isEmpty()) {
564                 return 1;
565             } else if (KDevelop::SessionController::isSessionRunning(session)) {
566                 pid = findSessionPid(session);
567             }
568         } else {
569             pid = getRunningSessionPid();
570         }
571 
572         if ( pid > 0 ) {
573             return openFilesInRunningInstance(initialFiles, pid) + openProjectInRunningInstance(initialDirectories, pid);
574         }
575         // else there are no running sessions, and the generated list of files will be opened below.
576 #endif
577     }
578 
579     // if empty, restart kdevelop with last active session, see SessionController::defaultSessionId
580     QString session;
581 
582     uint nRunningSessions = 0;
583     for (const KDevelop::SessionInfo& si : availableSessionInfos) {
584         if(KDevelop::SessionController::isSessionRunning(si.uuid.toString()))
585             ++nRunningSessions;
586     }
587 
588     // also show the picker dialog when a pid shall be retrieved and multiple
589     // sessions are running.
590     if(parser.isSet(QStringLiteral("pss")) || (parser.isSet(QStringLiteral("pid")) && !parser.isSet(QStringLiteral("open-session")) && !parser.isSet(QStringLiteral("ps")) && nRunningSessions > 1))
591     {
592         SessionInfos candidates;
593         for (const KDevelop::SessionInfo& si : availableSessionInfos) {
594             if( (!si.name.isEmpty() || !si.projects.isEmpty() || parser.isSet(QStringLiteral("pid"))) &&
595                 (!parser.isSet(QStringLiteral("pid")) || KDevelop::SessionController::isSessionRunning(si.uuid.toString())))
596                 candidates << si;
597         }
598 
599         if(candidates.size() == 0)
600         {
601             QTextStream qerr(stderr);
602             qerr << "no session available" << QLatin1Char('\n');
603             return 1;
604         }
605 
606         if(candidates.size() == 1 && parser.isSet(QStringLiteral("pid")))
607         {
608             session = candidates.constFirst().uuid.toString();
609         }else{
610             QTextStream qout(stdout);
611             for(int i = 0; i < candidates.size(); ++i)
612                 qout << "[" << i << "]: " << candidates.at(i).description << QLatin1Char('\n');
613             qout.flush();
614 
615             int chosen;
616             std::cin >> chosen;
617             if(std::cin.good() && (chosen >= 0 && chosen < candidates.size()))
618             {
619                 session = candidates.at(chosen).uuid.toString();
620             }else{
621                 QTextStream qerr(stderr);
622                 qerr << "invalid selection" << QLatin1Char('\n');
623                 return 1;
624             }
625         }
626     }
627 
628     if(parser.isSet(QStringLiteral("ps")))
629     {
630         bool onlyRunning = parser.isSet(QStringLiteral("pid"));
631         session = KDevelop::SessionController::showSessionChooserDialog(i18n("Select the session you would like to use"), onlyRunning);
632         if(session.isEmpty())
633             return 1;
634     }
635 
636     if ( parser.isSet(QStringLiteral("debug")) ) {
637         if ( debugArgs.isEmpty() ) {
638             QTextStream qerr(stderr);
639             qerr << QLatin1Char('\n') << i18nc("@info:shell", "Specify the executable you want to debug.") << QLatin1Char('\n');
640             return 1;
641         }
642 
643         QFileInfo executableFileInfo(debugArgs.first());
644         if (!executableFileInfo.exists()) {
645             executableFileInfo = QStandardPaths::findExecutable(debugArgs.first());
646             if (!executableFileInfo.exists()) {
647                 QTextStream qerr(stderr);
648                 qerr << QLatin1Char('\n') << i18nc("@info:shell", "Specified executable does not exist.") << QLatin1Char('\n');
649                 return 1;
650             }
651         }
652 
653         debugArgs.first() = executableFileInfo.absoluteFilePath();
654         debugeeName = i18n("Debug %1", executableFileInfo.fileName());
655         session = debugeeName;
656     } else if ( parser.isSet(QStringLiteral("new-session")) )
657     {
658         session = parser.value(QStringLiteral("new-session"));
659         for (const KDevelop::SessionInfo& si : availableSessionInfos) {
660             if ( session == si.name ) {
661                 QTextStream qerr(stderr);
662                 qerr << QLatin1Char('\n') << i18n("A session with the name %1 exists already. Use the -s switch to open it.", session) << QLatin1Char('\n');
663                 return 1;
664             }
665         }
666         // session doesn't exist, we can create it
667     } else if ( parser.isSet(QStringLiteral("open-session")) ) {
668         session = findSessionId(availableSessionInfos, parser.value(QStringLiteral("open-session")));
669         if (session.isEmpty()) {
670             return 1;
671         }
672     } else if ( parser.isSet(QStringLiteral("remove-session")) )
673     {
674         session = parser.value(QStringLiteral("remove-session"));
675         auto si = findSessionInList(availableSessionInfos, session);
676         if (!si) {
677             QTextStream qerr(stderr);
678             qerr << QLatin1Char('\n') << i18n("No session with the name %1 exists.", session) << QLatin1Char('\n');
679             return 1;
680         }
681 
682         auto sessionLock = KDevelop::SessionController::tryLockSession(si->uuid.toString());
683         if (!sessionLock.lock) {
684             QTextStream qerr(stderr);
685             qerr << QLatin1Char('\n') << i18n("Could not lock session %1 for deletion.", session) << QLatin1Char('\n');
686             return 1;
687         }
688         KDevelop::SessionController::deleteSessionFromDisk(sessionLock.lock);
689         QTextStream qout(stdout);
690         qout << QLatin1Char('\n') << i18n("Session with name %1 was successfully removed.", session) << QLatin1Char('\n');
691         return 0;
692     }
693 
694     if(parser.isSet(QStringLiteral("pid"))) {
695         if (session.isEmpty())
696         {   // just pick the first running session
697             for (const KDevelop::SessionInfo& si : availableSessionInfos) {
698                 if(KDevelop::SessionController::isSessionRunning(si.uuid.toString()))
699                     session = si.uuid.toString();
700             }
701         }
702         const KDevelop::SessionInfo* sessionData = findSessionInList(availableSessionInfos, session);
703 
704         if( !sessionData ) {
705             qCCritical(APP) << "session not given or does not exist";
706             return 5;
707         }
708 
709         const auto pid = findSessionPid(sessionData->uuid.toString());
710         if (pid > 0) {
711             // Print the PID and we're ready
712             std::cout << pid << std::endl;
713             return 0;
714         } else {
715             qCCritical(APP) << sessionData->uuid.toString() << sessionData->name << "is not running";
716             return 5;
717         }
718     }
719 
720     if (parser.isSet(QStringLiteral("project"))) {
721         const auto project = parser.value(QStringLiteral("project"));
722         QFileInfo info(project);
723         QUrl projectUrl;
724         if (info.suffix() == QLatin1String("kdev4")) {
725             projectUrl = QUrl::fromLocalFile(info.absoluteFilePath());
726         } else if (info.isDir()) {
727             QDir dir(info.absoluteFilePath());
728             const auto potentialProjectFiles = dir.entryList({QStringLiteral("*.kdev4")}, QDir::Files, QDir::Name);
729             qCDebug(APP) << "Found these potential project files:" << potentialProjectFiles;
730             if (!potentialProjectFiles.isEmpty()) {
731                 projectUrl = QUrl::fromLocalFile(dir.absoluteFilePath(potentialProjectFiles.value(0)));
732             }
733         } else {
734             QTextStream qerr(stderr);
735             qerr << "Invalid project: " << project << " - should be either a path to a .kdev4 file or a directory containing a .kdev4 file";
736             return 1;
737         }
738 
739         qCDebug(APP) << "Attempting to find a suitable session for project" << projectUrl;
740         const auto sessionInfos = findSessionsWithProject(availableSessionInfos, projectUrl);
741         qCDebug(APP) << "Found matching sessions:" << sessionInfos.size();
742         if (!sessionInfos.isEmpty()) {
743             // TODO: If there's more than one match: Allow the user to select which session to open?
744             qCDebug(APP) << "Attempting to open session:" << sessionInfos.at(0).name;
745             session = sessionInfos.at(0).uuid.toString();
746         }
747     }
748 
749     KDevIDEExtension::init();
750 
751     qCDebug(APP) << "Attempting to initialize session:" << session;
752     if(!Core::initialize(Core::Default, session))
753         return 5;
754 
755     // register a DBUS service for this process, so that we can open files in it from other invocations
756     QDBusConnection::sessionBus().registerService(QStringLiteral("org.kdevelop.kdevelop-%1").arg(app.applicationPid()));
757 
758     Core* core = Core::self();
759     if (!QProcessEnvironment::systemEnvironment().contains(QStringLiteral("KDEV_DISABLE_WELCOMEPAGE"))) {
760         core->pluginController()->loadPlugin(QStringLiteral("KDevWelcomePage"));
761     }
762 
763     const auto fetchUrlStrings = parser.values(QStringLiteral("fetch"));
764     for (const auto& fetchUrlString : fetchUrlStrings) {
765         core->projectControllerInternal()->fetchProjectFromUrl(QUrl::fromUserInput(fetchUrlString));
766     }
767 
768     const QString debugStr = QStringLiteral("debug");
769     if ( parser.isSet(debugStr) ) {
770         Q_ASSERT( !debugeeName.isEmpty() );
771         QString launchName = debugeeName;
772 
773         KDevelop::LaunchConfiguration* launch = nullptr;
774         qCDebug(APP) << launchName;
775         const auto launchconfigurations = core->runControllerInternal()->launchConfigurationsInternal();
776         for (KDevelop::LaunchConfiguration* l : launchconfigurations) {
777             qCDebug(APP) << l->name();
778             if (l->name() == launchName) {
779                 launch = l;
780             }
781         }
782 
783         KDevelop::LaunchConfigurationType *type = nullptr;
784         const auto launchConfigurationTypes = core->runController()->launchConfigurationTypes();
785         for (KDevelop::LaunchConfigurationType* t : launchConfigurationTypes) {
786             qCDebug(APP) << t->id();
787             if (t->id() == QLatin1String("Native Application")) {
788                 type = t;
789                 break;
790             }
791         }
792         if (!type) {
793             QTextStream qerr(stderr);
794             qerr << QLatin1Char('\n') << i18n("Cannot find native launch configuration type") << QLatin1Char('\n');
795             return 1;
796         }
797 
798         if (launch && launch->type()->id() != QLatin1String("Native Application")) launch = nullptr;
799         if (launch && launch->launcherForMode(debugStr) != parser.value(debugStr)) launch = nullptr;
800         if (!launch) {
801             qCDebug(APP) << launchName << "not found, creating a new one";
802             QPair<QString,QString> launcher;
803             launcher.first = debugStr;
804             const auto typeLaunchers = type->launchers();
805             for (KDevelop::ILauncher* l : typeLaunchers) {
806                 if (l->id() == parser.value(debugStr)) {
807                     if (l->supportedModes().contains(debugStr)) {
808                         launcher.second = l->id();
809                     }
810                 }
811             }
812             if (launcher.second.isEmpty()) {
813                 QTextStream qerr(stderr);
814                 qerr << QLatin1Char('\n') << i18n("Cannot find launcher %1", parser.value(debugStr)) << QLatin1Char('\n');
815                 return 1;
816             }
817             KDevelop::ILaunchConfiguration* ilaunch = core->runController()->createLaunchConfiguration(type, launcher, nullptr, launchName);
818             launch = static_cast<KDevelop::LaunchConfiguration*>(ilaunch);
819         }
820 
821         type->configureLaunchFromCmdLineArguments(launch->config(), debugArgs);
822         launch->config().writeEntry("Break on Start", true);
823         core->runControllerInternal()->setDefaultLaunch(launch);
824 
825         core->runControllerInternal()->execute(debugStr, launch);
826     } else {
827         openFiles(initialFiles);
828 
829         for(const auto& urlinfo: qAsConst(initialDirectories))
830             core->projectController()->openProjectForUrl(urlinfo.url);
831     }
832 
833 #if KDEVELOP_SINGLE_APP
834     // Set up remote arguments.
835     QObject::connect(&app, &SharedTools::QtSingleApplication::messageReceived,
836                      &app, &KDevelopApplication::remoteArguments);
837 
838     QObject::connect(&app, &SharedTools::QtSingleApplication::fileOpenRequest,
839                      &app, &KDevelopApplication::fileOpenRequested);
840 #endif
841 
842 
843     qCDebug(APP) << "Done startup" << "- took:" << timer.elapsed() << "ms";
844     timer.invalidate();
845 
846     return app.exec();
847 }
848 
849 #include "main.moc"
850