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