main.cpp 40.8 KB
Newer Older
Andreas Pakulat's avatar
Andreas Pakulat committed
1
/***************************************************************************
2
 *   Copyright 2003-2009 Alexander Dymo <adymo@kdevelop.org>               *
Manuel Breugelmans's avatar
Manuel Breugelmans committed
3 4 5 6 7 8 9 10
 *   Copyright 2007 Ralf Habacker  <Ralf.Habacker@freenet.de>              *
 *   Copyright 2006-2007 Matt Rogers  <mattr@kde.org>                      *
 *   Copyright 2006-2007 Hamish Rodda <rodda@kde.org>                      *
 *   Copyright 2005-2007 Adam Treat <treat@kde.org>                        *
 *   Copyright 2003-2007 Jens Dagerbo <jens.dagerbo@swipnet.se>            *
 *   Copyright 2001-2002 Bernd Gehrmann <bernd@mail.berlios.de>            *
 *   Copyright 2001-2002 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>       *
 *   Copyright 2003 Roberto Raggi <roberto@kdevelop.org>                   *
11
 *   Copyright 2010 Niko Sams <niko.sams@gmail.com>                        *
Kevin Funk's avatar
Kevin Funk committed
12
 *   Copyright 2015 Kevin Funk <kfunk@kde.org>                             *
Andreas Pakulat's avatar
Andreas Pakulat committed
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

30 31
#include "config-kdevelop.h"
#include "kdevelop_version.h"
32

33 34
#include "urlinfo.h"

35
#include <KLocalizedString>
Kevin Funk's avatar
Kevin Funk committed
36
#include <Kdelibs4ConfigMigrator>
37 38
#include <KAboutData>
#include <KCrash>
39

40
#include <QApplication>
41
#include <QElapsedTimer>
42 43
#include <QCommandLineParser>
#include <QCommandLineOption>
44
#include <QFileInfo>
45
#include <QProcessEnvironment>
46
#include <QSessionManager>
47
#include <QTextStream>
48 49
#include <QDBusInterface>
#include <QDBusReply>
50

51 52
#include <QQuickWindow>

53 54 55 56 57
#include <shell/core.h>
#include <shell/mainwindow.h>
#include <shell/projectcontroller.h>
#include <shell/documentcontroller.h>
#include <shell/plugincontroller.h>
58
#include <shell/sessioncontroller.h>
59 60 61 62 63
#include <shell/runcontroller.h>
#include <shell/launchconfiguration.h>
#include <shell/session.h>
#include <interfaces/ilauncher.h>
#include <interfaces/iproject.h>
Kevin Funk's avatar
Kevin Funk committed
64
#include <interfaces/launchconfigurationtype.h>
Milian Wolff's avatar
Milian Wolff committed
65
#include <util/path.h>
66
#include <debug.h>
67 68

#include "kdevideextension.h"
69 70 71
#if KDEVELOP_SINGLE_APP
#include "qtsingleapplication.h"
#endif
72

Milian Wolff's avatar
Milian Wolff committed
73
#include <iostream>
74

René J.V. Bertin's avatar
OS X:  
René J.V. Bertin committed
75 76 77 78
#ifdef Q_OS_MAC
#include <CoreFoundation/CoreFoundation.h>
#endif

Milian Wolff's avatar
Milian Wolff committed
79
using namespace KDevelop;
80

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
namespace {

#if KDEVELOP_SINGLE_APP
QString serializeOpenFilesMessage(const QVector<UrlInfo> &infos)
{
    QByteArray message;
    QDataStream stream(&message, QIODevice::WriteOnly);
    stream << QByteArrayLiteral("open");
    stream << infos;
    return QString::fromLatin1(message.toHex());
}
#endif

void openFiles(const QVector<UrlInfo>& infos)
{
96
    for (const UrlInfo& info : infos) {
97
        if (!ICore::self()->documentController()->openDocument(info.url, info.cursor)) {
98
            qWarning(APP) << i18n("Could not open %1", info.url.toDisplayString(QUrl::PreferLocalFile));
99 100 101 102 103 104 105
        }
    }
}

}

class KDevelopApplication:
106 107 108 109 110
#if KDEVELOP_SINGLE_APP
    public SharedTools::QtSingleApplication
#else
    public QApplication
#endif
111
{
112
    Q_OBJECT
113
public:
114
    explicit KDevelopApplication(int &argc, char **argv, bool GUIenabled = true)
115 116 117
#if KDEVELOP_SINGLE_APP
        : SharedTools::QtSingleApplication(QStringLiteral("KDevelop"), argc, argv)
#else
118
        : QApplication(argc, argv, GUIenabled)
119
#endif
120
        {
121
            Q_UNUSED(GUIenabled);
122 123
            connect(this, &QGuiApplication::saveStateRequest, this, &KDevelopApplication::saveState);
        }
124

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
#if KDEVELOP_SINGLE_APP
public Q_SLOTS:
    void remoteArguments(const QString &message, QObject *socket)
    {
        Q_UNUSED(socket);

        QByteArray ba = QByteArray::fromHex(message.toLatin1());
        QDataStream stream(ba);
        QByteArray command;
        stream >> command;

        qCDebug(APP) << "Received remote command: " << command;

        if (command == "open") {
            QVector<UrlInfo> infos;
            stream >> infos;
141 142 143 144 145 146 147 148 149 150 151

            QVector<UrlInfo> files, directories;
            for (const auto& info : infos)
                if (info.isDirectory())
                    directories << info;
                else
                    files << info;

            openFiles(files);
            for(const auto &urlinfo : directories)
                ICore::self()->projectController()->openProjectForUrl(urlinfo.url);
152 153 154 155 156 157 158 159 160 161 162
        } else {
            qCWarning(APP) << "Unknown remote command: " << command;
        }
    }

    void fileOpenRequested(const QString &file)
    {
        openFiles({UrlInfo(file)});
    }
#endif

163
private Q_SLOTS:
164
    void saveState( QSessionManager& sm ) {
165
        if (KDevelop::Core::self() && KDevelop::Core::self()->sessionController()) {
166 167
            const auto activeSession = KDevelop::Core::self()->sessionController()->activeSession();
            if (!activeSession) {
168
                qWarning(APP) << "No active session, can't save state";
169 170
                return;
            }
171

172
            const QString x11SessionId = sm.sessionId() + QLatin1Char('_') + sm.sessionKey();
173
            QString kdevelopSessionId = activeSession->id().toString();
174 175 176 177 178 179 180
            sm.setRestartCommand({
                QCoreApplication::applicationFilePath(),
                QStringLiteral("-session"),
                x11SessionId,
                QStringLiteral("-s"),
                kdevelopSessionId
            });
181
        }
182 183 184
    }
};

185 186 187
/// Tries to find a session identified by @p data in @p sessions.
/// The @p data may be either a session's name or a string-representation of its UUID.
/// @return pointer to the session or NULL if nothing appropriate has been found
188
static const KDevelop::SessionInfo* findSessionInList( const SessionInfos& sessions, const QString& data )
189 190 191 192
{
    // We won't search a session without input data, since that could lead to false-positives
    // with unnamed sessions
    if( data.isEmpty() )
193
        return nullptr;
194

195
    for( auto it = sessions.constBegin(); it != sessions.constEnd(); ++it ) {
196 197 198 199 200
        if ( ( it->name == data ) || ( it->uuid.toString() == data ) ) {
            const KDevelop::SessionInfo& sessionRef = *it;
            return &sessionRef;
        }
    }
201
    return nullptr;
202 203
}

Kevin Funk's avatar
Kevin Funk committed
204 205 206 207 208 209 210
/// Tries to find sessions containing project @p projectUrl in @p sessions.
static const KDevelop::SessionInfos findSessionsWithProject(const SessionInfos& sessions, const QUrl& projectUrl)
{
    if (!projectUrl.isValid())
        return {};

    KDevelop::SessionInfos infos;
211 212 213
    for (auto& session : sessions) {
        if (session.projects.contains(projectUrl)) {
            infos << session;
Kevin Funk's avatar
Kevin Funk committed
214 215 216 217 218
        }
    }
    return infos;
}

219 220
/// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid
/// Returns the exit status
221
static int openFilesInRunningInstance(const QVector<UrlInfo>& files, qint64 pid)
222
{
223 224
    const QString service = QStringLiteral("org.kdevelop.kdevelop-%1").arg(pid);
    QDBusInterface iface(service, QStringLiteral("/org/kdevelop/DocumentController"), QStringLiteral("org.kdevelop.DocumentController"));
225 226

    QStringList urls;
Yuri Chornoivan's avatar
Yuri Chornoivan committed
227
    bool errors_occurred = false;
228
    for (const UrlInfo& file : files) {
229
        QDBusReply<bool> result = iface.call(QStringLiteral("openDocumentSimple"), file.url.toString(), file.cursor.line(), file.cursor.column());
230 231
        if ( ! result.value() ) {
            QTextStream err(stderr);
232
            err << i18n("Could not open file '%1'.", file.url.toDisplayString(QUrl::PreferLocalFile)) << "\n";
Yuri Chornoivan's avatar
Yuri Chornoivan committed
233
            errors_occurred = true;
234
        }
235
    }
236
    // make the window visible
237 238
    QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"),
                                                               QStringLiteral("ensureVisible") );
239
    QDBusConnection::sessionBus().asyncCall( makeVisible );
Yuri Chornoivan's avatar
Yuri Chornoivan committed
240
    return errors_occurred;
241 242
}

243 244 245 246 247 248 249 250
/// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid
/// Returns the exit status
static int openProjectInRunningInstance(const QVector<UrlInfo>& paths, qint64 pid)
{
    const QString service = QStringLiteral("org.kdevelop.kdevelop-%1").arg(pid);
    QDBusInterface iface(service, QStringLiteral("/org/kdevelop/ProjectController"), QStringLiteral("org.kdevelop.ProjectController"));
    int errors = 0;

251
    for (const UrlInfo& path : paths) {
252 253 254 255 256 257 258 259 260 261 262 263 264 265
        QDBusReply<void> result = iface.call(QStringLiteral("openProjectForUrl"), path.url.toString());
        if ( !result.isValid() ) {
            QTextStream err(stderr);
            err << i18n("Could not open project '%1': %2", path.url.toDisplayString(QUrl::PreferLocalFile), result.error().message()) << "\n";
            ++errors;
        }
    }
    // make the window visible
    QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"),
                                                               QStringLiteral("ensureVisible") );
    QDBusConnection::sessionBus().asyncCall( makeVisible );
    return errors;
}

266 267
/// Gets the PID of a running KDevelop instance, eventually asking the user if there is more than one.
/// Returns -1 in case there are no running sessions.
268
static qint64 getRunningSessionPid()
269
{
270
    SessionInfos candidates;
271 272
    const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos();
    for (const KDevelop::SessionInfo& si : availableSessionInfos) {
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
        if( KDevelop::SessionController::isSessionRunning(si.uuid.toString()) ) {
            candidates << si;
        }
    }
    if ( candidates.isEmpty() ) {
        return -1;
    }

    QString sessionUuid;
    if ( candidates.size() == 1 ) {
        sessionUuid = candidates.first().uuid.toString();
    }
    else {
        const QString title = i18n("Select the session to open the document in");
        sessionUuid = KDevelop::SessionController::showSessionChooserDialog(title, true);
    }
    return KDevelop::SessionController::sessionRunInfo(sessionUuid).holderPid;
}

292
static QString findSessionId(const SessionInfos& availableSessionInfos, const QString& session)
293 294 295 296
{
    //If there is a session and a project with the same name, always open the session
    //regardless of the order encountered
    QString projectAsSession;
297
    for (const KDevelop::SessionInfo& si : availableSessionInfos) {
298 299 300
        if ( session == si.name || session == si.uuid.toString() ) {
            return si.uuid.toString();
        } else if (projectAsSession.isEmpty()) {
301
            for (const QUrl& k : si.projects) {
302
                QString fn(k.fileName());
303
                fn = fn.left(fn.indexOf(QLatin1Char('.')));
304 305 306 307 308 309 310 311 312
                if ( session == fn ) {
                    projectAsSession = si.uuid.toString();
                }
            }
        }
    }

    if (projectAsSession.isEmpty())  {
        QTextStream qerr(stderr);
313 314
        qerr << QLatin1Char('\n') << i18n("Cannot open unknown session %1. See `--list-sessions` switch for available sessions or use `-n` to create a new one.",
                             session) << QLatin1Char('\n');
315 316 317 318 319 320 321 322 323 324
    }
    return projectAsSession;
}

static qint64 findSessionPid(const QString &sessionId)
{
    KDevelop::SessionRunInfo sessionInfo = KDevelop::SessionController::sessionRunInfo( sessionId );
    return sessionInfo.holderPid;
}

Adam Treat's avatar
Adam Treat committed
325
int main( int argc, char *argv[] )
326
{
327 328 329
    QElapsedTimer timer;
    timer.start();

330 331 332 333 334 335 336
#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
    // If possible, use the Software backend for QQuickWidget (currently used in the
    // welcome page plugin). This means we don't need OpenGL at all, avoiding issues
    // like https://bugs.kde.org/show_bug.cgi?id=386527.
    QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software);
#endif

337 338
    // TODO: Maybe generalize, add KDEVELOP_STANDALONE build option
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
Kevin Funk's avatar
Kevin Funk committed
339 340 341
    qputenv("KDE_FORK_SLAVES", "1"); // KIO slaves will be forked off instead of being started via DBus
#endif

Kevin Funk's avatar
Kevin Funk committed
342 343 344 345 346 347
    // Useful for valgrind runs, just `export KDEV_DISABLE_JIT=1`
    if (qEnvironmentVariableIsSet("KDEV_DISABLE_JIT")) {
        qputenv("KDEV_DISABLE_WELCOMEPAGE", "1");
        qputenv("QT_ENABLE_REGEXP_JIT", "0");
    }

348
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
349
    QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
350

René J.V. Bertin's avatar
OS X:  
René J.V. Bertin committed
351 352 353 354 355 356 357 358 359 360 361 362 363 364
#ifdef Q_OS_MAC
    CFBundleRef mainBundle = CFBundleGetMainBundle();
    if (mainBundle) {
        // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist,
        // for regular executables it is obtained in another way.
        CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle);
        if (infoDict) {
            // Try to prevent App Nap on OS X. This can be tricky in practice, at least in 10.9 .
            CFDictionarySetValue(infoDict, CFSTR("NSAppSleepDisabled"), kCFBooleanTrue);
            CFDictionarySetValue(infoDict, CFSTR("NSSupportsAutomaticTermination"), kCFBooleanFalse);
        }
    }
#endif

365 366 367 368 369 370 371 372 373 374
    //we can't use KCmdLineArgs as it doesn't allow arguments for the debugee
    //so lookup the --debug switch and eat everything behind by decrementing argc
    //debugArgs is filled with args after --debug <debuger>
    QStringList debugArgs;
    QString debugeeName;
    {
        bool debugFound = false;
        int c = argc;
        for (int i=0; i < c; ++i) {
            if (debugFound) {
375
                debugArgs << QString::fromUtf8(argv[i]);
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
376 377
            } else if ((qstrcmp(argv[i], "--debug") == 0) || (qstrcmp(argv[i], "-d") == 0)) {
                if (argc > (i + 1)) {
378 379
                    i++;
                }
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
380
                argc = i + 1;
381
                debugFound = true;
382
            } else if (QByteArray(argv[i]).startsWith("--debug=")) {
383 384 385 386 387 388 389
                argc = i + 1;
                debugFound = true;
            }
        }
    }

    KDevelopApplication app(argc, argv);
390
    KLocalizedString::setApplicationDomain("kdevelop");
391

392
    KAboutData aboutData( QStringLiteral("kdevelop"), i18n("KDevelop"), QStringLiteral(KDEVELOP_VERSION_STRING),
393
                          i18n("The KDevelop Integrated Development Environment"),
394
                          KAboutLicense::GPL,
395
                          i18n("Copyright 1999-%1, The KDevelop developers", QStringLiteral("2020")),
396
                          QString(), QStringLiteral("https://www.kdevelop.org/"));
397
    aboutData.setDesktopFileName(QStringLiteral("org.kde.kdevelop"));
398
    aboutData.addAuthor( i18n("Kevin Funk"), i18n( "Co-maintainer, C++/Clang, QA, Windows Support, Performance, Website" ), QStringLiteral("kfunk@kde.org") );
399
    aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Co-maintainer, AppImage, Python Support, User Interface improvements" ), QStringLiteral("svenbrauch@gmail.com") );
400
    aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "CMake Support, Run Support, Kross Support" ), QStringLiteral("aleixpol@kde.org") );
401 402 403 404 405 406 407 408 409
    aboutData.addAuthor( i18n("Milian Wolff"), i18n( "C++/Clang, Generic manager, Webdevelopment Plugins, Snippets, Performance" ), QStringLiteral("mail@milianw.de") );
    aboutData.addAuthor( i18n("Olivier JG"), i18n( "C++/Clang, DUChain, Bug Fixes" ), QStringLiteral("olivier.jg@gmail.com") );
    aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), QStringLiteral("apaku@gmx.de") );
    aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), QStringLiteral("adymo@kdevelop.org") );
    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") );
    aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), QStringLiteral("ghost@cs.msu.su") );
    aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), QStringLiteral("rodda@kde.org") );
    aboutData.addAuthor( i18n("Amilcar do Carmo Lucas"), i18n( "Website admin, API documentation, Doxygen and autoproject patches" ), QStringLiteral("amilcar@kdevelop.org") );
    aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), QStringLiteral("niko.sams@gmail.com") );
410
    aboutData.addAuthor( i18n("Friedrich W. H. Kossebau"), QString(), QStringLiteral("kossebau@kde.org") );
411 412 413 414

    aboutData.addCredit( i18n("Matt Rogers"), QString(), QStringLiteral("mattr@kde.org"));
    aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), QStringLiteral("cedric.pasteur@free.fr") );
    aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), QStringLiteral("powerfox@kde.ru") );
415
    // QTest integration is separate in playground currently.
416
    //aboutData.addCredit( i18n("Manuel Breugelmanns"), i18n( "Veritas, QTest integration"), "mbr.nxi@gmail.com" );
417 418 419 420 421 422 423
    aboutData.addCredit( i18n("Robert Gruber") , i18n( "SnippetPart, debugger and usability patches" ), QStringLiteral("rgruber@users.sourceforge.net") );
    aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), QStringLiteral("dukjuahn@gmail.com") );
    aboutData.addCredit( i18n("Harald Fernengel"), i18n( "Ported to Qt 3, patches, valgrind, diff and perforce support" ), QStringLiteral("harry@kdevelop.org") );
    aboutData.addCredit( i18n("Roberto Raggi"), i18n( "C++ parser" ), QStringLiteral("roberto@kdevelop.org") );
    aboutData.addCredit( i18n("The KWrite authors"), i18n( "Kate editor component" ), QStringLiteral("kwrite-devel@kde.org") );
    aboutData.addCredit( i18n("Nokia Corporation/Qt Software"), i18n( "Designer code" ), QStringLiteral("qt-info@nokia.com") );

424
    aboutData.addCredit( i18n("Contributors to older versions:"), QString(), QString() );
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
    aboutData.addCredit( i18n("Bernd Gehrmann"), i18n( "Initial idea, basic architecture, much initial source code" ), QStringLiteral("bernd@kdevelop.org") );
    aboutData.addCredit( i18n("Caleb Tennis"), i18n( "KTabBar, bugfixes" ), QStringLiteral("caleb@aei-tech.com") );
    aboutData.addCredit( i18n("Richard Dale"), i18n( "Java & Objective C support" ), QStringLiteral("Richard_Dale@tipitina.demon.co.uk") );
    aboutData.addCredit( i18n("John Birch"), i18n( "Debugger frontend" ), QStringLiteral("jbb@kdevelop.org") );
    aboutData.addCredit( i18n("Sandy Meier"), i18n( "PHP support, context menu stuff" ), QStringLiteral("smeier@kdevelop.org") );
    aboutData.addCredit( i18n("Kurt Granroth"), i18n( "KDE application templates" ), QStringLiteral("kurth@granroth.org") );
    aboutData.addCredit( i18n("Ian Reinhart Geiser"), i18n( "Dist part, bash support, application templates" ), QStringLiteral("geiseri@yahoo.com") );
    aboutData.addCredit( i18n("Matthias Hoelzer-Kluepfel"), i18n( "Several components, htdig indexing" ), QStringLiteral("hoelzer@kde.org") );
    aboutData.addCredit( i18n("Victor Roeder"), i18n( "Help with Automake manager and persistent class store" ), QStringLiteral("victor_roeder@gmx.de") );
    aboutData.addCredit( i18n("Simon Hausmann"), i18n( "Help with KParts infrastructure" ), QStringLiteral("hausmann@kde.org") );
    aboutData.addCredit( i18n("Oliver Kellogg"), i18n( "Ada support" ), QStringLiteral("okellogg@users.sourceforge.net") );
    aboutData.addCredit( i18n("Jakob Simon-Gaarde"), i18n( "QMake projectmanager" ), QStringLiteral("jsgaarde@tdcspace.dk") );
    aboutData.addCredit( i18n("Falk Brettschneider"), i18n( "MDI modes, QEditor, bugfixes" ), QStringLiteral("falkbr@kdevelop.org") );
    aboutData.addCredit( i18n("Mario Scalas"), i18n( "PartExplorer, redesign of CvsPart, patches, bugs(fixes)" ), QStringLiteral("mario.scalas@libero.it") );
    aboutData.addCredit( i18n("Jens Dagerbo"), i18n( "Replace, Bookmarks, FileList and CTags2 plugins. Overall improvements and patches" ), QStringLiteral("jens.dagerbo@swipnet.se") );
    aboutData.addCredit( i18n("Julian Rockey"), i18n( "Filecreate part and other bits and patches" ), QStringLiteral("linux@jrockey.com") );
    aboutData.addCredit( i18n("Ajay Guleria"), i18n( "ClearCase support" ), QStringLiteral("ajay_guleria@yahoo.com") );
    aboutData.addCredit( i18n("Marek Janukowicz"), i18n( "Ruby support" ), QStringLiteral("child@t17.ds.pwr.wroc.pl") );
    aboutData.addCredit( i18n("Robert Moniot"), i18n( "Fortran documentation" ), QStringLiteral("moniot@fordham.edu") );
    aboutData.addCredit( i18n("Ka-Ping Yee"), i18n( "Python documentation utility" ), QStringLiteral("ping@lfw.org") );
    aboutData.addCredit( i18n("Dimitri van Heesch"), i18n( "Doxygen wizard" ), QStringLiteral("dimitri@stack.nl") );
    aboutData.addCredit( i18n("Hugo Varotto"), i18n( "Fileselector component" ), QStringLiteral("hugo@varotto-usa.com") );
    aboutData.addCredit( i18n("Matt Newell"), i18n( "Fileselector component" ), QStringLiteral("newellm@proaxis.com") );
    aboutData.addCredit( i18n("Daniel Engelschalt"), i18n( "C++ code completion, persistent class store" ), QStringLiteral("daniel.engelschalt@gmx.net") );
    aboutData.addCredit( i18n("Stephane Ancelot"), i18n( "Patches" ), QStringLiteral("sancelot@free.fr") );
    aboutData.addCredit( i18n("Jens Zurheide"), i18n( "Patches" ), QStringLiteral("jens.zurheide@gmx.de") );
    aboutData.addCredit( i18n("Luc Willems"), i18n( "Help with Perl support" ), QStringLiteral("Willems.luc@pandora.be") );
    aboutData.addCredit( i18n("Marcel Turino"), i18n( "Documentation index view" ), QStringLiteral("M.Turino@gmx.de") );
    aboutData.addCredit( i18n("Yann Hodique"), i18n( "Patches" ), QStringLiteral("Yann.Hodique@lifl.fr") );
    aboutData.addCredit( i18n("Tobias Gl\303\244\303\237er") , i18n( "Documentation Finder,  qmake projectmanager patches, usability improvements, bugfixes ... " ), QStringLiteral("tobi.web@gmx.de") );
    aboutData.addCredit( i18n("Andreas Koepfle") , i18n( "QMake project manager patches" ), QStringLiteral("koepfle@ti.uni-mannheim.de") );
    aboutData.addCredit( i18n("Sascha Cunz") , i18n( "Cleanup and bugfixes for qEditor, AutoMake and much other stuff" ), QStringLiteral("mail@sacu.de") );
    aboutData.addCredit( i18n("Zoran Karavla"), i18n( "Artwork for the ruby language" ), QStringLiteral("webmaster@the-error.net"), QStringLiteral("http://the-error.net") );
Adam Treat's avatar
Adam Treat committed
458

459 460
    KAboutData::setApplicationData(aboutData);
    // set icon for shells which do not use desktop file metadata
461 462
    // but without setting replacing an existing icon with an empty one!
    QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("kdevelop"), QApplication::windowIcon()));
Kevin Funk's avatar
Kevin Funk committed
463

464 465
    KCrash::initialize();

466 467 468 469 470
    Kdelibs4ConfigMigrator migrator(QStringLiteral("kdevelop"));
    migrator.setConfigFiles({QStringLiteral("kdeveloprc")});
    migrator.setUiFiles({QStringLiteral("kdevelopui.rc")});
    migrator.migrate();

Kevin Funk's avatar
Kevin Funk committed
471 472 473
    // High DPI support
    app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);

474
    qCDebug(APP) << "Startup";
Kevin Funk's avatar
Kevin Funk committed
475

476 477 478
    QCommandLineParser parser;
    aboutData.setupCommandLine(&parser);

479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
    parser.addOption(QCommandLineOption{QStringList{QStringLiteral("n"), QStringLiteral("new-session")},
                     i18n("Open KDevelop with a new session using the given name."),
                     QStringLiteral("name")});
    parser.addOption(QCommandLineOption{QStringList{QStringLiteral("s"), QStringLiteral("open-session")},
                     i18n("Open KDevelop with the given session.\n"
                          "You can pass either hash or the name of the session."),
                     QStringLiteral("session")});
    parser.addOption(QCommandLineOption{QStringList{QStringLiteral("rm"), QStringLiteral("remove-session")},
                     i18n("Delete the given session.\n"
                          "You can pass either hash or the name of the session." ),
                     QStringLiteral("session")});
    parser.addOption(QCommandLineOption{QStringList{QStringLiteral("ps"), QStringLiteral("pick-session")},
                     i18n("Shows all available sessions and lets you select one to open.")});
    parser.addOption(QCommandLineOption{QStringList{QStringLiteral("pss"), QStringLiteral("pick-session-shell")},
                     i18n("List all available sessions on shell and lets you select one to open.")});
    parser.addOption(QCommandLineOption{QStringList{QStringLiteral("l"), QStringLiteral("list-sessions")},
                     i18n("List available sessions and quit.")});
    parser.addOption(QCommandLineOption{QStringList{QStringLiteral("f"), QStringLiteral("fetch")},
                     i18n("Open KDevelop and fetch the project from the given <repo url>."),
                     QStringLiteral("repo url")});
    parser.addOption(QCommandLineOption{QStringList{QStringLiteral("p"), QStringLiteral("project")},
                     i18n("Open KDevelop and load the given project. <project> can be either a .kdev4 file or a directory path."),
                     QStringLiteral("project")});
    parser.addOption(QCommandLineOption{QStringList{QStringLiteral("d"), QStringLiteral("debug")},
503
                     i18n("Start debugging an application in KDevelop with the given debugger.\n"
504
                     "The executable that should be debugged must follow - including arguments.\n"
505
                     "Example: kdevelop --debug gdb myapp --foo bar"), QStringLiteral("debugger")});
506

507 508 509 510 511 512 513
    // this is used by the 'kdevelop!' script to retrieve the pid of a KDEVELOP
    // instance. When this is called, then we should just print the PID on the
    // standard-output. If a session is specified through open-session, then
    // we should return the PID of that session. Otherwise, if only a single
    // session is running, then we should just return the PID of that session.
    // Otherwise, we should print a command-line session-chooser dialog ("--pss"),
    // which only shows the running sessions, and the user can pick one.
514
    parser.addOption(QCommandLineOption{QStringList{QStringLiteral("pid")}});
515

516 517
    parser.addPositionalArgument(QStringLiteral("files"),
                     i18n( "Files to load, or directories to load as projects" ), QStringLiteral("[FILE[:line[:column]] | DIRECTORY]..."));
518

519 520
    // The session-controller needs to arguments to eventually pass them to newly opened sessions
    KDevelop::SessionController::setArguments(argc, argv);
521

522 523 524
    parser.process(app);
    aboutData.processCommandLine(&parser);

525
    if(parser.isSet(QStringLiteral("list-sessions")))
Milian Wolff's avatar
Milian Wolff committed
526 527
    {
        QTextStream qout(stdout);
528 529
        qout << QLatin1Char('\n') << ki18n("Available sessions (use '-s HASH' or '-s NAME' to open a specific one):").toString() << QLatin1String("\n\n");
        qout << QStringLiteral("%1").arg(ki18n("Hash").toString(), -38) << '\t' << ki18n("Name: Opened Projects").toString() << QLatin1Char('\n');
530 531
        const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos();
        for (const KDevelop::SessionInfo& si : availableSessionInfos) {
532
            if ( si.name.isEmpty() && si.projects.isEmpty() ) {
533 534
                continue;
            }
535
            qout << si.uuid.toString() << '\t' << si.description;
536

537
            if(KDevelop::SessionController::isSessionRunning(si.uuid.toString()))
538
                qout << "     " << i18n("[running]");
539

540
            qout << QLatin1Char('\n');
Milian Wolff's avatar
Milian Wolff committed
541 542 543
        }
        return 0;
    }
544

545
    // Handle extra arguments, which stand for files to open
546
    QVector<UrlInfo> initialFiles;
547
    QVector<UrlInfo> initialDirectories;
548 549
    const auto files = parser.positionalArguments();
    for (const QString& file : files) {
550 551 552 553 554 555
        const UrlInfo info(file);
        if (info.isDirectory()) {
            initialDirectories.append(info);
        } else {
            initialFiles.append(info);
        }
556
    }
557

558 559
    const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos();

560
    if ((!initialFiles.isEmpty() || !initialDirectories.isEmpty()) && !parser.isSet(QStringLiteral("new-session"))) {
561 562
#if KDEVELOP_SINGLE_APP
        if (app.isRunning()) {
563
            bool success = app.sendMessage(serializeOpenFilesMessage(initialFiles << initialDirectories));
564 565 566 567 568
            if (success) {
                return 0;
            }
        }
#else
569
        qint64 pid = -1;
570 571
        if (parser.isSet(QStringLiteral("open-session"))) {
            const QString session = findSessionId(availableSessionInfos, parser.value(QStringLiteral("open-session")));
572 573 574 575 576 577 578 579
            if (session.isEmpty()) {
                return 1;
            } else if (KDevelop::SessionController::isSessionRunning(session)) {
                pid = findSessionPid(session);
            }
        } else {
            pid = getRunningSessionPid();
        }
580

581
        if ( pid > 0 ) {
582
            return openFilesInRunningInstance(initialFiles, pid) + openProjectInRunningInstance(initialDirectories, pid);
583 584
        }
        // else there are no running sessions, and the generated list of files will be opened below.
585
#endif
586 587
    }

588 589
    // if empty, restart kdevelop with last active session, see SessionController::defaultSessionId
    QString session;
590

591
    uint nRunningSessions = 0;
592
    for (const KDevelop::SessionInfo& si : availableSessionInfos) {
593 594
        if(KDevelop::SessionController::isSessionRunning(si.uuid.toString()))
            ++nRunningSessions;
595
    }
596 597 598

    // also show the picker dialog when a pid shall be retrieved and multiple
    // sessions are running.
599
    if(parser.isSet(QStringLiteral("pss")) || (parser.isSet(QStringLiteral("pid")) && !parser.isSet(QStringLiteral("open-session")) && !parser.isSet(QStringLiteral("ps")) && nRunningSessions > 1))
600 601
    {
        QTextStream qerr(stderr);
602
        SessionInfos candidates;
603
        for (const KDevelop::SessionInfo& si : availableSessionInfos) {
604 605
            if( (!si.name.isEmpty() || !si.projects.isEmpty() || parser.isSet(QStringLiteral("pid"))) &&
                (!parser.isSet(QStringLiteral("pid")) || KDevelop::SessionController::isSessionRunning(si.uuid.toString())))
606
                candidates << si;
607
        }
Dāvis Mosāns's avatar
Dāvis Mosāns committed
608

609 610
        if(candidates.size() == 0)
        {
611
            qerr << "no session available" << QLatin1Char('\n');
612 613
            return 1;
        }
Dāvis Mosāns's avatar
Dāvis Mosāns committed
614

615
        if(candidates.size() == 1 && parser.isSet(QStringLiteral("pid")))
616 617 618 619
        {
            session = candidates[0].uuid.toString();
        }else{
            for(int i = 0; i < candidates.size(); ++i)
620
                qerr << "[" << i << "]: " << candidates[i].description << QLatin1Char('\n');
Dāvis Mosāns's avatar
Dāvis Mosāns committed
621

622 623
            int chosen;
            std::cin >> chosen;
624
            if(std::cin.good() && (chosen >= 0 && chosen < candidates.size()))
625 626 627
            {
                session = candidates[chosen].uuid.toString();
            }else{
628
                qerr << "invalid selection" << QLatin1Char('\n');
629 630 631 632
                return 1;
            }
        }
    }
Dāvis Mosāns's avatar
Dāvis Mosāns committed
633

634
    if(parser.isSet(QStringLiteral("ps")))
635
    {
636
        bool onlyRunning = parser.isSet(QStringLiteral("pid"));
637
        session = KDevelop::SessionController::showSessionChooserDialog(i18n("Select the session you would like to use"), onlyRunning);
638 639 640
        if(session.isEmpty())
            return 1;
    }
641

642
    if ( parser.isSet(QStringLiteral("debug")) ) {
643 644
        if ( debugArgs.isEmpty() ) {
            QTextStream qerr(stderr);
645
            qerr << QLatin1Char('\n') << i18nc("@info:shell", "Specify the executable you want to debug.") << QLatin1Char('\n');
646 647
            return 1;
        }
648

649 650 651 652
        QFileInfo executableFileInfo(debugArgs.first());
        if (!executableFileInfo.exists()) {
            executableFileInfo = QStandardPaths::findExecutable(debugArgs.first());
            if (!executableFileInfo.exists()) {
653
                QTextStream qerr(stderr);
654
                qerr << QLatin1Char('\n') << i18nc("@info:shell", "Specified executable does not exist.") << QLatin1Char('\n');
655 656 657 658
                return 1;
            }
        }

659 660
        debugArgs.first() = executableFileInfo.absoluteFilePath();
        debugeeName = i18n("Debug %1", executableFileInfo.fileName());
661
        session = debugeeName;
662
    } else if ( parser.isSet(QStringLiteral("new-session")) )
663
    {
664
        session = parser.value(QStringLiteral("new-session"));
665
        for (const KDevelop::SessionInfo& si : availableSessionInfos) {
666 667
            if ( session == si.name ) {
                QTextStream qerr(stderr);
668
                qerr << QLatin1Char('\n') << i18n("A session with the name %1 exists already. Use the -s switch to open it.", session) << QLatin1Char('\n');
669 670 671 672
                return 1;
            }
        }
        // session doesn't exist, we can create it
673 674
    } else if ( parser.isSet(QStringLiteral("open-session")) ) {
        session = findSessionId(availableSessionInfos, parser.value(QStringLiteral("open-session")));
675 676
        if (session.isEmpty()) {
            return 1;
677
        }
678
    } else if ( parser.isSet(QStringLiteral("remove-session")) )
679
    {
680
        session = parser.value(QStringLiteral("remove-session"));
681
        auto si = findSessionInList(availableSessionInfos, session);
682 683
        if (!si) {
            QTextStream qerr(stderr);
684
            qerr << QLatin1Char('\n') << i18n("No session with the name %1 exists.", session) << QLatin1Char('\n');
685 686 687 688 689 690
            return 1;
        }

        auto sessionLock = KDevelop::SessionController::tryLockSession(si->uuid.toString());
        if (!sessionLock.lock) {
            QTextStream qerr(stderr);
691
            qerr << QLatin1Char('\n') << i18n("Could not lock session %1 for deletion.", session) << QLatin1Char('\n');
692 693 694 695
            return 1;
        }
        KDevelop::SessionController::deleteSessionFromDisk(sessionLock.lock);
        QTextStream qout(stdout);
696
        qout << QLatin1Char('\n') << i18n("Session with name %1 was successfully removed.", session) << QLatin1Char('\n');
697
        return 0;
698
    }
699

700
    if(parser.isSet(QStringLiteral("pid"))) {
701 702
        if (session.isEmpty())
        {   // just pick the first running session
703
            for (const KDevelop::SessionInfo& si : availableSessionInfos) {
704 705
                if(KDevelop::SessionController::isSessionRunning(si.uuid.toString()))
                    session = si.uuid.toString();
706
            }
707
        }
708
        const KDevelop::SessionInfo* sessionData = findSessionInList(availableSessionInfos, session);
709

710
        if( !sessionData ) {
711
            qCritical(APP) << "session not given or does not exist";
712
            return 5;
713 714
        }

715 716
        const auto pid = findSessionPid(sessionData->uuid.toString());
        if (pid > 0) {
717
            // Print the PID and we're ready
718
            std::cout << pid << std::endl;
719
            return 0;
720
        } else {
721
            qCritical(APP) << sessionData->uuid.toString() << sessionData->name << "is not running";
722
            return 5;
723
        }
724
    }
725

726
    if (parser.isSet(QStringLiteral("project"))) {
Kevin Funk's avatar
Kevin Funk committed
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
        const auto project = parser.value(QStringLiteral("project"));
        QFileInfo info(project);
        QUrl projectUrl;
        if (info.suffix() == QLatin1String("kdev4")) {
            projectUrl = QUrl::fromLocalFile(info.absoluteFilePath());
        } else if (info.isDir()) {
            QDir dir(info.absoluteFilePath());
            const auto potentialProjectFiles = dir.entryList({QStringLiteral("*.kdev4")}, QDir::Files, QDir::Name);
            qDebug(APP) << "Found these potential project files:" << potentialProjectFiles;
            if (!potentialProjectFiles.isEmpty()) {
                projectUrl = QUrl::fromLocalFile(dir.absoluteFilePath(potentialProjectFiles.value(0)));
            }
        } else {
            QTextStream qerr(stderr);
            qerr << "Invalid project: " << project << " - should be either a path to a .kdev4 file or a directory containing a .kdev4 file";
            return 1;
        }

        qDebug(APP) << "Attempting to find a suitable session for project" << projectUrl;
        const auto sessionInfos = findSessionsWithProject(availableSessionInfos, projectUrl);
        qDebug(APP) << "Found matching sessions:" << sessionInfos.size();
        if (!sessionInfos.isEmpty()) {
            // TODO: If there's more than one match: Allow the user to select which session to open?
            qDebug(APP) << "Attempting to open session:" << sessionInfos.at(0).name;
            session = sessionInfos.at(0).uuid.toString();
        }
    }

755
    KDevIDEExtension::init();
756

Kevin Funk's avatar
Kevin Funk committed
757
    qDebug(APP) << "Attempting to initialize session:" << session;
Kevin Funk's avatar
Kevin Funk committed
758
    if(!Core::initialize(Core::Default, session))
759
        return 5;
760

761
    // register a DBUS service for this process, so that we can open files in it from other invocations
762
    QDBusConnection::sessionBus().registerService(QStringLiteral("org.kdevelop.kdevelop-%1").arg(app.applicationPid()));
763

764
    Core* core = Core::self();
765 766
    if (!QProcessEnvironment::systemEnvironment().contains(QStringLiteral("KDEV_DISABLE_WELCOMEPAGE"))) {
        core->pluginController()->loadPlugin(QStringLiteral("KDevWelcomePage"));
767
    }
768

769 770 771 772 773
    const auto fetchUrlStrings = parser.values(QStringLiteral("fetch"));
    for (const auto& fetchUrlString : fetchUrlStrings) {
        core->projectControllerInternal()->fetchProjectFromUrl(QUrl::fromUserInput(fetchUrlString));
    }

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
774 775
    const QString debugStr = QStringLiteral("debug");
    if ( parser.isSet(debugStr) ) {
776 777
        Q_ASSERT( !debugeeName.isEmpty() );
        QString launchName = debugeeName;
778

779
        KDevelop::LaunchConfiguration* launch = nullptr;
Dāvis Mosāns's avatar
Dāvis Mosāns committed
780
        qCDebug(APP) << launchName;
781 782
        const auto launchconfigurations = core->runControllerInternal()->launchConfigurationsInternal();
        for (KDevelop::LaunchConfiguration* l : launchconfigurations) {
Dāvis Mosāns's avatar
Dāvis Mosāns committed
783
            qCDebug(APP) << l->name();
784 785 786 787 788
            if (l->name() == launchName) {
                launch = l;
            }
        }

789
        KDevelop::LaunchConfigurationType *type = nullptr;
790 791
        const auto launchConfigurationTypes = core->runController()->launchConfigurationTypes();
        for (KDevelop::LaunchConfigurationType* t : launchConfigurationTypes) {
Dāvis Mosāns's avatar
Dāvis Mosāns committed
792
            qCDebug(APP) << t->id();
793
            if (t->id() == QLatin1String("Native Application")) {
794 795 796 797 798 799
                type = t;
                break;
            }
        }
        if (!type) {
            QTextStream qerr(stderr);
800
            qerr << QLatin1Char('\n') << i18n("Cannot find native launch configuration type") << QLatin1Char('\n');
801 802 803
            return 1;
        }

804
        if (launch && launch->type()->id() != QLatin1String("Native Application")) launch = nullptr;
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
805
        if (launch && launch->launcherForMode(debugStr) != parser.value(debugStr)) launch = nullptr;
806
        if (!launch) {
Dāvis Mosāns's avatar
Dāvis Mosāns committed
807
            qCDebug(APP) << launchName << "not found, creating a new one";
808
            QPair<QString,QString> launcher;
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
809
            launcher.first = debugStr;
810 811
            const auto typeLaunchers = type->launchers();
            for (KDevelop::ILauncher* l : typeLaunchers) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
812 813
                if (l->id() == parser.value(debugStr)) {
                    if (l->supportedModes().contains(debugStr)) {
814 815 816 817 818 819
                        launcher.second = l->id();
                    }
                }
            }
            if (launcher.second.isEmpty()) {
                QTextStream qerr(stderr);
820
                qerr << QLatin1Char('\n') << i18n("Cannot find launcher %1", parser.value(debugStr)) << QLatin1Char('\n');
821 822
                return 1;
            }
823
            KDevelop::ILaunchConfiguration* ilaunch = core->runController()->createLaunchConfiguration(type, launcher, nullptr, launchName);
824
            launch = static_cast<KDevelop::LaunchConfiguration*>(ilaunch);
825 826 827 828 829 830
        }

        type->configureLaunchFromCmdLineArguments(launch->config(), debugArgs);
        launch->config().writeEntry("Break on Start", true);
        core->runControllerInternal()->setDefaultLaunch(launch);

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
831
        core->runControllerInternal()->execute(debugStr, launch);
832
    } else {
833
        openFiles(initialFiles);
834

835
        for(const auto& urlinfo: qAsConst(initialDirectories))
836
            core->projectController()->openProjectForUrl(urlinfo.url);
837
    }
838

839 840 841 842 843 844 845 846 847 848
#if KDEVELOP_SINGLE_APP
    // Set up remote arguments.
    QObject::connect(&app, &SharedTools::QtSingleApplication::messageReceived,
                     &app, &KDevelopApplication::remoteArguments);

    QObject::connect(&app, &SharedTools::QtSingleApplication::fileOpenRequest,
                     &app, &KDevelopApplication::fileOpenRequested);
#endif


849 850 851
    qCDebug(APP) << "Done startup" << "- took:" << timer.elapsed() << "ms";
    timer.invalidate();

852
    return app.exec();
853
}