File indexing completed on 2024-04-28 05:49:27

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
0003    SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "kateapp.h"
0009 
0010 #include "katemainwindow.h"
0011 #include "kateviewmanager.h"
0012 
0013 #include <kcoreaddons_version.h>
0014 
0015 #include <KAboutData>
0016 #include <KConfigGui>
0017 #include <KCrash>
0018 #include <KLazyLocalizedString>
0019 #include <KLocalizedString>
0020 #include <KMessageBox>
0021 #include <KNetworkMounts>
0022 #include <KSharedConfig>
0023 
0024 // signal handler for SIGINT & SIGTERM
0025 #ifdef Q_OS_UNIX
0026 #include <KSignalHandler>
0027 #include <signal.h>
0028 #include <unistd.h>
0029 #endif
0030 
0031 // X11 startup handling
0032 #define HAVE_X11 __has_include(<KStartupInfo>)
0033 #if HAVE_X11
0034 #include <KStartupInfo>
0035 #endif
0036 
0037 #ifdef WITH_KUSERFEEDBACK
0038 #include <KUserFeedback/ApplicationVersionSource>
0039 #include <KUserFeedback/PlatformInfoSource>
0040 #include <KUserFeedback/QtVersionSource>
0041 #include <KUserFeedback/ScreenInfoSource>
0042 #include <KUserFeedback/StartCountSource>
0043 #include <KUserFeedback/UsageTimeSource>
0044 #endif
0045 
0046 #include <QApplication>
0047 #include <QCommandLineParser>
0048 #include <QDBusConnection>
0049 #include <QFileInfo>
0050 #include <QFileOpenEvent>
0051 #include <QJsonArray>
0052 #include <QJsonDocument>
0053 #include <QLoggingCategory>
0054 #include <QRegularExpression>
0055 #include <QStringDecoder>
0056 #include <QTimer>
0057 #include <QUrlQuery>
0058 
0059 #include <urlinfo.h>
0060 
0061 #ifndef Q_OS_WIN
0062 #include <unistd.h>
0063 #ifndef Q_OS_HAIKU
0064 #include <libintl.h>
0065 #endif
0066 #endif
0067 
0068 #include <iostream>
0069 
0070 #ifdef Q_OS_WIN
0071 #include <windows.h>
0072 #endif
0073 
0074 #ifdef HAVE_CTERMID
0075 #include <fcntl.h>
0076 #include <sys/stat.h>
0077 #include <sys/types.h>
0078 #include <termios.h>
0079 #include <unistd.h>
0080 #endif
0081 
0082 #if HAVE_DAEMON
0083 #include <unistd.h>
0084 #endif
0085 
0086 // remember if we were started inside a terminal
0087 static bool insideTerminal = false;
0088 
0089 /**
0090  * singleton instance pointer
0091  */
0092 static KateApp *appSelf = Q_NULLPTR;
0093 
0094 Q_LOGGING_CATEGORY(LOG_KATE, "kate", QtWarningMsg)
0095 
0096 void KateApp::initPreApplicationCreation(bool detach)
0097 {
0098 #if !defined(Q_OS_WIN) && !defined(Q_OS_HAIKU)
0099     // Prohibit using sudo or kdesu (but allow using the root user directly)
0100     if (getuid() == 0) {
0101         setlocale(LC_ALL, "");
0102         bindtextdomain("kate", KDE_INSTALL_FULL_LOCALEDIR);
0103         if (!qEnvironmentVariableIsEmpty("SUDO_USER")) {
0104             auto message = kli18n(
0105                 "Running this editor with sudo can cause bugs and expose you to security vulnerabilities. "
0106                 "Instead use this editor normally and you will be prompted for elevated privileges when "
0107                 "saving documents if needed.");
0108             std::cout << dgettext("kate", message.untranslatedText()) << std::endl;
0109             exit(EXIT_FAILURE);
0110         } else if (!qEnvironmentVariableIsEmpty("KDESU_USER")) {
0111             auto message = kli18n(
0112                 "Running this editor with kdesu can cause bugs and expose you to security vulnerabilities. "
0113                 "Instead use this editor normally and you will be prompted for elevated privileges when "
0114                 "saving documents if needed.");
0115             std::cout << dgettext("kate", message.untranslatedText()) << std::endl;
0116             exit(EXIT_FAILURE);
0117         }
0118     }
0119 #endif
0120 
0121     /**
0122      * enable dark mode for title bar on Windows
0123      */
0124 #if defined(Q_OS_WIN)
0125     if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) {
0126         qputenv("QT_QPA_PLATFORM", "windows:darkmode=1");
0127     }
0128 #endif
0129 
0130     /**
0131      * allow fractional scaling
0132      * we only activate this on Windows, it seems to creates problems on unices
0133      * (and there the fractional scaling with the QT_... env vars as set by KScreen works)
0134      * see bug 416078
0135      *
0136      * we switched to Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor because of font rendering issues
0137      * we follow what Krita does here, see https://invent.kde.org/graphics/krita/-/blob/master/krita/main.cc
0138      * we raise the Qt requirement to  5.15 as it seems some patches went in after 5.14 that are needed
0139      * see Krita comments, too
0140      */
0141 #if defined(Q_OS_WIN)
0142     QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
0143 #endif
0144 
0145 #ifdef Q_OS_WIN
0146     // Enable on windows to see output in console
0147     if (AttachConsole(ATTACH_PARENT_PROCESS)) {
0148         // we are inside a terminal
0149         insideTerminal = true;
0150 
0151         // don't enable output if we shall detach, to avoid terminal pollution
0152         if (!detach) {
0153             if (fileno(stdout) < 0)
0154                 freopen("CON", "w", stdout);
0155             if (fileno(stderr) < 0)
0156                 freopen("CON", "w", stderr);
0157         }
0158     }
0159 #endif
0160 
0161 #ifdef HAVE_CTERMID
0162     /**
0163      * https://stackoverflow.com/questions/1312922/detect-if-stdin-is-a-terminal-or-pipe-in-c-c-qt
0164      */
0165     char tty[L_ctermid + 1] = {0};
0166     ctermid(tty);
0167     if (int fd = ::open(tty, O_RDONLY); fd >= 0) {
0168         insideTerminal = true;
0169         ::close(fd);
0170     }
0171 #endif
0172 
0173     // blacklist macOS, crashes on start with this
0174     // TODO: investigate why
0175 #if !defined(Q_OS_MACOS) && defined(HAVE_DAEMON)
0176     if (detach) {
0177         // just try it, if it doesn't work we just continue in the foreground
0178         const int ret = daemon(1, 0 /* close in and outputs to avoid pollution of shell */);
0179         (void)ret;
0180     }
0181 #endif
0182 }
0183 
0184 KateApp::KateApp(const QCommandLineParser &args, const ApplicationMode mode, const QString &sessionsDir)
0185     : m_args(args)
0186     , m_mode(mode)
0187     , m_wrapper(appSelf = this)
0188     , m_adaptor(this)
0189     , m_docManager(this)
0190     , m_sessionManager(this, sessionsDir)
0191     , m_stashManager(this)
0192     , m_lastActivationChange(QDateTime::currentMSecsSinceEpoch())
0193 {
0194     /**
0195      * For Windows and macOS: use Breeze if available
0196      * Of all tested styles that works the best for us
0197      */
0198 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
0199     QApplication::setStyle(QStringLiteral("breeze"));
0200 #endif
0201 
0202     /**
0203      * Enable crash handling through KCrash.
0204      */
0205     KCrash::initialize();
0206 
0207     /**
0208      * re-route some signals to application wrapper
0209      */
0210     connect(&m_docManager, &KateDocManager::documentCreated, &m_wrapper, &KTextEditor::Application::documentCreated);
0211     connect(&m_docManager, &KateDocManager::documentWillBeDeleted, &m_wrapper, &KTextEditor::Application::documentWillBeDeleted);
0212     connect(&m_docManager, &KateDocManager::documentDeleted, &m_wrapper, &KTextEditor::Application::documentDeleted);
0213 
0214     /**
0215      * handle mac os x like file open request via event filter
0216      */
0217     qApp->installEventFilter(this);
0218 
0219 #ifdef WITH_KUSERFEEDBACK
0220     /**
0221      * defaults, inspired by plasma
0222      * important: choose between kate and kwrite mode here to submit the right product id
0223      */
0224     m_userFeedbackProvider.setProductIdentifier(isKate() ? QStringLiteral("org.kde.kate") : QStringLiteral("org.kde.kwrite"));
0225     m_userFeedbackProvider.setFeedbackServer(QUrl(QStringLiteral("https://telemetry.kde.org/")));
0226     m_userFeedbackProvider.setSubmissionInterval(7);
0227     m_userFeedbackProvider.setApplicationStartsUntilEncouragement(5);
0228     m_userFeedbackProvider.setEncouragementDelay(30);
0229 
0230     /**
0231      * add some feedback providers
0232      */
0233 
0234     // software version info
0235     m_userFeedbackProvider.addDataSource(new KUserFeedback::ApplicationVersionSource);
0236     m_userFeedbackProvider.addDataSource(new KUserFeedback::QtVersionSource);
0237 
0238     // info about the machine
0239     m_userFeedbackProvider.addDataSource(new KUserFeedback::PlatformInfoSource);
0240     m_userFeedbackProvider.addDataSource(new KUserFeedback::ScreenInfoSource);
0241 
0242     // usage info
0243     m_userFeedbackProvider.addDataSource(new KUserFeedback::StartCountSource);
0244     m_userFeedbackProvider.addDataSource(new KUserFeedback::UsageTimeSource);
0245 #endif
0246 }
0247 
0248 KateApp::~KateApp()
0249 {
0250     // we want no auto saving during application closing, we handle that explicitly
0251     KateSessionManager::AutoSaveBlocker blocker(sessionManager());
0252 
0253     /**
0254      * unregister from dbus before we get unusable...
0255      */
0256     if (QDBusConnection::sessionBus().interface()) {
0257         m_adaptor.emitExiting();
0258         QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/MainApplication"));
0259     }
0260 
0261     /**
0262      * delete all main windows before the document manager & co. die
0263      */
0264     while (!m_mainWindows.isEmpty()) {
0265         // mainwindow itself calls KateApp::removeMainWindow(this)
0266         delete m_mainWindows[0];
0267     }
0268 }
0269 
0270 KateApp *KateApp::self()
0271 {
0272     return appSelf;
0273 }
0274 
0275 void KateApp::fillAuthorsAndCredits(KAboutData &aboutData)
0276 {
0277     aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("https://cullmann.io"));
0278     aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org"));
0279     aboutData.addAuthor(i18n("Sven Brauch"), i18n("Developer"), QStringLiteral("mail@svenbrauch.de"));
0280     aboutData.addAuthor(i18n("Kåre Särs"), i18n("Developer"), QStringLiteral("kare.sars@iki.fi"));
0281     aboutData.addAuthor(i18n("Waqar Ahmed"), i18n("Core Developer"), QStringLiteral("waqar.17a@gmail.com"));
0282     aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("https://www.alweb.dk"));
0283     aboutData.addAuthor(i18n("Joseph Wenninger"),
0284                         i18n("Core Developer"),
0285                         QStringLiteral("jowenn@kde.org"),
0286                         QStringLiteral("http://stud3.tuwien.ac.at/~e9925371"));
0287     aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org"));
0288     aboutData.addAuthor(i18n("Alexander Neundorf"), i18n("Developer"), QStringLiteral("neundorf@kde.org"));
0289     aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org"));
0290     aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org"));
0291     aboutData.addAuthor(i18n("Matt Newell"), i18n("Testing, ..."), QStringLiteral("newellm@proaxis.com"));
0292     aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at"));
0293     aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz"));
0294     aboutData.addAuthor(i18n("Jochen Wilhemly"), i18n("KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de"));
0295     aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org"));
0296     aboutData.addAuthor(i18n("Christian Gebauer"), QString(), QStringLiteral("gebauer@kde.org"));
0297     aboutData.addAuthor(i18n("Simon Hausmann"), QString(), QStringLiteral("hausmann@kde.org"));
0298     aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com"));
0299     aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net"));
0300     aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org"));
0301     aboutData.addAuthor(i18n("Pablo Martín"),
0302                         i18n("Python Plugin Developer"),
0303                         QStringLiteral("goinnn@gmail.com"),
0304                         QStringLiteral("https://github.com/goinnn/"));
0305     aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"),
0306                         i18n("QA and Scripting"),
0307                         QStringLiteral("oss@senarclens.eu"),
0308                         QStringLiteral("http://find-santa.eu/"));
0309 
0310     aboutData.addCredit(i18n("Tyson Tan"),
0311                         i18n("Designer of Kate's mascot 'Kate the Cyber Woodpecker'"),
0312                         QString(),
0313                         QStringLiteral("https://www.tysontan.com/"));
0314     aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it"));
0315     aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu"));
0316     aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL"));
0317     aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite"));
0318     aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG"));
0319     aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX"));
0320     aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python"));
0321     aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python"));
0322     aboutData.addCredit(i18n("Daniel Naber"));
0323     aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme"));
0324     aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list"));
0325     aboutData.addCredit(i18n("Carsten Pfeiffer"), i18n("Very nice help"));
0326     aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention"));
0327 }
0328 
0329 bool KateApp::init()
0330 {
0331     // we want no auto saving during application startup, we handle that explicitly
0332     KateSessionManager::AutoSaveBlocker blocker(sessionManager());
0333 
0334     // set KATE_PID for use in child processes
0335     if (isKate()) {
0336         qputenv("KATE_PID", QStringLiteral("%1").arg(QCoreApplication::applicationPid()).toLatin1().constData());
0337     }
0338 
0339 #ifdef Q_OS_UNIX
0340     /**
0341      * Set up signal handler for SIGINT and SIGTERM
0342      */
0343     KSignalHandler::self()->watchSignal(SIGINT);
0344     KSignalHandler::self()->watchSignal(SIGTERM);
0345     connect(KSignalHandler::self(), &KSignalHandler::signalReceived, this, [this](int signal) {
0346         if (signal == SIGINT || signal == SIGTERM) {
0347             printf("Shutting down...\n");
0348             quit();
0349         }
0350     });
0351 #endif
0352 
0353     // handle restore different
0354     if (qApp->isSessionRestored()) {
0355         restoreKate();
0356     } else {
0357         // let us handle our command line args and co ;)
0358         // we can exit here if session chooser decides
0359         if (!startupKate()) {
0360             // session chooser told to exit kate
0361             return false;
0362         }
0363     }
0364 
0365     return true;
0366 }
0367 
0368 void KateApp::restoreKate()
0369 {
0370     // we want no auto saving during application startup, we handle that explicitly
0371     KateSessionManager::AutoSaveBlocker blocker(sessionManager());
0372 
0373     KConfig *sessionConfig = KConfigGui::sessionConfig();
0374 
0375     // activate again correct session!!!
0376     QString lastSession(sessionConfig->group(QStringLiteral("General")).readEntry("Last Session", QString()));
0377     sessionManager()->activateSession(lastSession, false, false);
0378 
0379     // plugins
0380     KateApp::self()->pluginManager()->loadConfig(sessionConfig);
0381 
0382     // restore the files we need
0383     m_docManager.restoreDocumentList(sessionConfig);
0384 
0385     // restore all windows ;)
0386     for (int n = 1; KMainWindow::canBeRestored(n); n++) {
0387         newMainWindow(sessionConfig, QString::number(n));
0388     }
0389 
0390     // oh, no mainwindow, create one, should not happen, but make sure ;)
0391     if (mainWindowsCount() == 0) {
0392         newMainWindow();
0393     }
0394 }
0395 
0396 bool KateApp::startupKate()
0397 {
0398     // we want no auto saving during application startup, we handle that explicitly
0399     KateSessionManager::AutoSaveBlocker blocker(sessionManager());
0400 
0401     // KWrite is session less
0402     if (isKWrite()) {
0403         sessionManager()->activateAnonymousSession();
0404     } else {
0405         // user specified session to open
0406         if (m_args.isSet(QStringLiteral("start"))) {
0407             sessionManager()->activateSession(m_args.value(QStringLiteral("start")), false);
0408         } else if (m_args.isSet(QStringLiteral("startanon"))) {
0409             sessionManager()->activateAnonymousSession();
0410         } else if (!m_args.isSet(QStringLiteral("stdin")) && (m_args.positionalArguments().count() == 0)) { // only start session if no files specified
0411             // let the user choose session if possible
0412             if (!sessionManager()->chooseSession()) {
0413 #if HAVE_X11
0414                 // we will exit kate now, notify the rest of the world we are done
0415                 KStartupInfo::appStarted();
0416 #endif
0417 
0418                 return false;
0419             }
0420         } else {
0421             sessionManager()->activateAnonymousSession();
0422         }
0423     }
0424 
0425     // oh, no mainwindow, create one, should not happen, but make sure ;)
0426     if (mainWindowsCount() == 0) {
0427         newMainWindow();
0428     }
0429 
0430     bool tempfileSet = m_args.isSet(QStringLiteral("tempfile"));
0431 
0432     KTextEditor::Document *doc = nullptr;
0433     const QString codec_name = m_args.isSet(QStringLiteral("encoding")) ? m_args.value(QStringLiteral("encoding")) : QString();
0434 
0435     const auto args = m_args.positionalArguments();
0436 
0437     for (const auto &positionalArgument : args) {
0438         UrlInfo info(positionalArgument);
0439 
0440         // this file is no local dir, open it, else warn
0441         bool noDir = !info.url.isLocalFile()
0442             || KNetworkMounts::self()->isOptionEnabledForPath(info.url.toLocalFile(), KNetworkMounts::LowSideEffectsOptimizations)
0443             || !QFileInfo(info.url.toLocalFile()).isDir();
0444 
0445         if (noDir) {
0446             if (!info.cursor.isValid()) {
0447                 if (hasCursorInArgs()) {
0448                     info.cursor = cursorFromArgs();
0449                 } else if (info.url.hasQuery()) {
0450                     info.cursor = cursorFromQueryString(info.url);
0451                 }
0452             }
0453             doc = openDocUrl(info.url, codec_name, tempfileSet, /*activateView=*/false, info.cursor);
0454         } else if (!KateApp::self()->pluginManager()->plugin(QStringLiteral("kateprojectplugin"))) {
0455             KMessageBox::error(activeKateMainWindow(), i18n("Folders can only be opened when the projects plugin is enabled"));
0456         }
0457     }
0458 
0459     // handle stdin input
0460     if (m_args.isSet(QStringLiteral("stdin"))) {
0461         QFile input;
0462         input.open(stdin, QIODevice::ReadOnly);
0463         auto decoder = QStringDecoder(codec_name.toUtf8().constData());
0464         QString text = decoder.isValid() ? decoder.decode(input.readAll()) : QString::fromLocal8Bit(input.readAll());
0465 
0466         // normalize line endings, to e.g. catch issues with \r\n on Windows
0467         text.replace(QRegularExpression(QStringLiteral("\r\n?")), QStringLiteral("\n"));
0468 
0469         openInput(text, codec_name);
0470     } else if (doc) {
0471         activeKateMainWindow()->viewManager()->activateView(doc);
0472     }
0473 
0474     return true;
0475 }
0476 
0477 void KateApp::shutdownKate(KateMainWindow *win)
0478 {
0479     // we want no auto saving during application closing, we handle that explicitly
0480     KateSessionManager::AutoSaveBlocker blocker(sessionManager());
0481 
0482     if (!win->queryClose_internal()) {
0483         return;
0484     }
0485 
0486     sessionManager()->saveActiveSession(true);
0487     stashManager()->stashDocuments(sessionManager()->activeSession()->config(), documentManager()->documentList());
0488 
0489     /**
0490      * all main windows will be cleaned up
0491      * in the KateApp destructor after the event
0492      * loop is left
0493      *
0494      * NOTE: From Qt 6, quit() will ask all windows to close,
0495      * but we already do our cleanup (and save prompts) when
0496      * the event loop quits, so we explicitly call exit() here.
0497      */
0498     QApplication::exit();
0499 }
0500 
0501 KatePluginManager *KateApp::pluginManager()
0502 {
0503     return &m_pluginManager;
0504 }
0505 
0506 KateDocManager *KateApp::documentManager()
0507 {
0508     return &m_docManager;
0509 }
0510 
0511 KateSessionManager *KateApp::sessionManager()
0512 {
0513     return &m_sessionManager;
0514 }
0515 
0516 KateStashManager *KateApp::stashManager()
0517 {
0518     return &m_stashManager;
0519 }
0520 
0521 KTextEditor::Document *KateApp::openDocUrl(const QUrl &url, const QString &encoding, bool isTempFile, bool activateView, KTextEditor::Cursor c)
0522 {
0523     // temporary file handling
0524     // ensure we will delete the local file we opened via --tempfile at end of program
0525     // we can only do this properly for local files
0526     isTempFile = (isTempFile && !url.isEmpty() && url.isLocalFile() && QFile::exists(url.toLocalFile()));
0527 
0528     KateMainWindow *mainWindow = activeKateMainWindow();
0529 
0530     if (!mainWindow) {
0531         return nullptr;
0532     }
0533 
0534     // this file is no local dir, open it, else warn
0535     bool noDir = !url.isLocalFile() || KNetworkMounts::self()->isOptionEnabledForPath(url.toLocalFile(), KNetworkMounts::LowSideEffectsOptimizations)
0536         || !QFileInfo(url.toLocalFile()).isDir();
0537 
0538     KTextEditor::Document *doc = nullptr;
0539 
0540     if (noDir) {
0541         KateDocumentInfo docInfo;
0542         docInfo.startCursor = c;
0543         doc = mainWindow->viewManager()->openUrl(url, encoding, activateView, isTempFile, docInfo);
0544     } else {
0545         KMessageBox::error(mainWindow, i18n("The file '%1' could not be opened: it is not a normal file, it is a folder.", url.url()));
0546     }
0547 
0548     // document was successfully opened, ensure we will handle destroy properly
0549     if (doc) {
0550         // connect to slot & register the temp file handling if needed
0551         connect(doc, &QObject::destroyed, this, &KateApp::openDocUrlDocumentDestroyed);
0552         if (isTempFile) {
0553             m_tempFilesToDelete[doc].push_back(url.toLocalFile());
0554         }
0555     }
0556 
0557     // unable to open document, directly dispose of the temporary file
0558     else if (isTempFile) {
0559         QFile::remove(url.toLocalFile());
0560     }
0561 
0562     return doc;
0563 }
0564 
0565 void KateApp::openDocUrlDocumentDestroyed(QObject *document)
0566 {
0567     // do we need to kill the temporary files for this document?
0568     if (const auto tempFilesIt = m_tempFilesToDelete.find(document); tempFilesIt != m_tempFilesToDelete.end()) {
0569         for (const auto &file : tempFilesIt.value()) {
0570             QFile::remove(file);
0571         }
0572         m_tempFilesToDelete.erase(tempFilesIt);
0573     }
0574 
0575     // emit token signal to unblock remove blocking instances
0576     m_adaptor.emitDocumentClosed(QString::number(reinterpret_cast<qptrdiff>(document)));
0577 }
0578 
0579 KTextEditor::Cursor KateApp::cursorFromArgs()
0580 {
0581     bool hasLine = m_args.isSet(QStringLiteral("line"));
0582     bool hasColumn = m_args.isSet(QStringLiteral("column"));
0583 
0584     if (!hasLine && !hasColumn) {
0585         return KTextEditor::Cursor::invalid();
0586     }
0587 
0588     int line = qMax(m_args.value(QStringLiteral("line")).toInt() - 1, 0);
0589     int column = qMax(m_args.value(QStringLiteral("column")).toInt() - 1, 0);
0590 
0591     return {line, column};
0592 }
0593 
0594 KTextEditor::Cursor KateApp::cursorFromQueryString(const QUrl &url)
0595 {
0596     if (!url.hasQuery()) {
0597         return KTextEditor::Cursor::invalid();
0598     }
0599 
0600     QUrlQuery urlQuery(url);
0601     QString lineStr = urlQuery.queryItemValue(QStringLiteral("line"));
0602     QString columnStr = urlQuery.queryItemValue(QStringLiteral("column"));
0603 
0604     if (lineStr.isEmpty() && columnStr.isEmpty()) {
0605         return KTextEditor::Cursor::invalid();
0606     }
0607 
0608     int line = qMax(urlQuery.queryItemValue(QStringLiteral("line")).toInt() - 1, 0);
0609     int column = qMax(urlQuery.queryItemValue(QStringLiteral("column")).toInt() - 1, 0);
0610 
0611     return {line, column};
0612 }
0613 
0614 bool KateApp::setCursor(int line, int column)
0615 {
0616     KateMainWindow *mainWindow = activeKateMainWindow();
0617 
0618     if (!mainWindow) {
0619         return false;
0620     }
0621 
0622     if (auto v = mainWindow->viewManager()->activeView()) {
0623         v->removeSelection();
0624         v->setCursorPosition(KTextEditor::Cursor(line, column));
0625     }
0626 
0627     return true;
0628 }
0629 
0630 bool KateApp::hasCursorInArgs()
0631 {
0632     return m_args.isSet(QStringLiteral("line")) || m_args.isSet(QStringLiteral("column"));
0633 }
0634 
0635 bool KateApp::openInput(const QString &text, const QString &encoding)
0636 {
0637     activeKateMainWindow()->viewManager()->openUrl(QUrl(), encoding, true);
0638 
0639     if (!activeKateMainWindow()->viewManager()->activeView()) {
0640         return false;
0641     }
0642 
0643     KTextEditor::Document *doc = activeKateMainWindow()->viewManager()->activeView()->document();
0644 
0645     if (!doc) {
0646         return false;
0647     }
0648 
0649     return doc->setText(text);
0650 }
0651 
0652 KTextEditor::MainWindow *KateApp::activeMainWindow()
0653 {
0654     // either return wrapper or nullptr
0655     if (KateMainWindow *a = activeKateMainWindow()) {
0656         return a->wrapper();
0657     }
0658     return nullptr;
0659 }
0660 
0661 QList<KTextEditor::MainWindow *> KateApp::mainWindows()
0662 {
0663     // assemble right list
0664     QList<KTextEditor::MainWindow *> windows;
0665     windows.reserve(m_mainWindows.size());
0666 
0667     for (const auto mainWindow : std::as_const(m_mainWindows)) {
0668         windows.push_back(mainWindow->wrapper());
0669     }
0670     return windows;
0671 }
0672 
0673 KateMainWindow *KateApp::newMainWindow(KConfig *sconfig_, const QString &sgroup_, bool userTriggered)
0674 {
0675     KConfig *sconfig = sconfig_ ? sconfig_ : KSharedConfig::openConfig().data();
0676     QString sgroup = !sgroup_.isEmpty() ? sgroup_ : QStringLiteral("MainWindow0");
0677 
0678     KateMainWindow *mainWindow = new KateMainWindow(sconfig, sgroup, userTriggered);
0679     mainWindow->show();
0680 
0681     return mainWindow;
0682 }
0683 
0684 void KateApp::addMainWindow(KateMainWindow *mainWindow)
0685 {
0686     m_mainWindows.push_back(mainWindow);
0687 }
0688 
0689 void KateApp::removeMainWindow(KateMainWindow *mainWindow)
0690 {
0691     m_mainWindows.removeAll(mainWindow);
0692 }
0693 
0694 KateMainWindow *KateApp::activeKateMainWindow()
0695 {
0696     if (m_mainWindows.isEmpty()) {
0697         return nullptr;
0698     }
0699 
0700     int n = m_mainWindows.indexOf(qApp->activeWindow());
0701 
0702     if (n < 0) {
0703         n = 0;
0704     }
0705 
0706     return m_mainWindows[n];
0707 }
0708 
0709 int KateApp::mainWindowsCount() const
0710 {
0711     return m_mainWindows.size();
0712 }
0713 
0714 int KateApp::mainWindowID(KateMainWindow *window)
0715 {
0716     return m_mainWindows.indexOf(window);
0717 }
0718 
0719 KateMainWindow *KateApp::mainWindow(int n)
0720 {
0721     if (n < m_mainWindows.size()) {
0722         return m_mainWindows[n];
0723     }
0724 
0725     return nullptr;
0726 }
0727 
0728 bool KateApp::closeDocuments(const QList<KTextEditor::Document *> &documents)
0729 {
0730     bool shutdownKate =
0731         KateApp::self()->activeKateMainWindow()->modCloseAfterLast() && KateApp::self()->documentManager()->documentList().size() == documents.size();
0732     bool success = m_docManager.closeDocumentList(documents, KateApp::self()->activeKateMainWindow());
0733 
0734     if (success && shutdownKate) {
0735         QTimer::singleShot(0, this, []() {
0736             KateApp::self()->shutdownKate(KateApp::self()->activeKateMainWindow());
0737         });
0738         return true;
0739     }
0740 
0741     return success;
0742 }
0743 
0744 KTextEditor::Plugin *KateApp::plugin(const QString &name)
0745 {
0746     return m_pluginManager.plugin(name);
0747 }
0748 
0749 bool KateApp::eventFilter(QObject *obj, QEvent *event)
0750 {
0751     // keep track when we got activated last time
0752     if (event->type() == QEvent::ActivationChange) {
0753         m_lastActivationChange = QDateTime::currentMSecsSinceEpoch();
0754     }
0755 
0756     /**
0757      * handle mac os like file open
0758      */
0759     else if (event->type() == QEvent::FileOpen) {
0760         /**
0761          * try to open and activate the new document, like we would do for stuff
0762          * opened via dbus
0763          */
0764         QFileOpenEvent *foe = static_cast<QFileOpenEvent *>(event);
0765         KTextEditor::Document *doc = openDocUrl(foe->url(), QString(), false);
0766         if (doc && activeKateMainWindow()) {
0767             activeKateMainWindow()->viewManager()->activateView(doc);
0768         }
0769         return true;
0770     }
0771 
0772     /**
0773      * else: pass over to default implementation
0774      */
0775     return QObject::eventFilter(obj, event);
0776 }
0777 
0778 void KateApp::remoteMessageReceived(quint32, QByteArray message)
0779 {
0780     /**
0781      * try to parse message, ignore if no object
0782      */
0783     const QJsonDocument jsonMessage = QJsonDocument::fromJson(message);
0784     if (!jsonMessage.isObject()) {
0785         return;
0786     }
0787 
0788     KTextEditor::Document *doc = nullptr;
0789     /**
0790      * open all passed urls
0791      */
0792     const QJsonArray urls = jsonMessage.object().value(QLatin1String("urls")).toArray();
0793     for (const QJsonValue &urlObject : urls) {
0794         /**
0795          * get url meta data
0796          */
0797         const QUrl url = urlObject.toObject().value(QLatin1String("url")).toVariant().toUrl();
0798         const int line = urlObject.toObject().value(QLatin1String("line")).toVariant().toInt();
0799         const int column = urlObject.toObject().value(QLatin1String("column")).toVariant().toInt();
0800 
0801         /**
0802          * open file + save line/column if requested
0803          */
0804         doc = openDocUrl(url, QString(), false, /*activateView=*/false, KTextEditor::Cursor{line, column});
0805     }
0806 
0807     // try to activate current window
0808     m_adaptor.activate();
0809     if (doc && activeMainWindow()) {
0810         activeMainWindow()->activateView(doc);
0811     }
0812 }
0813 
0814 bool KateApp::documentVisibleInOtherWindows(KTextEditor::Document *doc, KateMainWindow *window) const
0815 {
0816     for (auto win : m_mainWindows) {
0817         if (win != window && win->viewManager()->viewspaceCountForDoc(doc) > 0) {
0818             return true;
0819         }
0820     }
0821     return false;
0822 }
0823 
0824 bool KateApp::isInsideTerminal()
0825 {
0826     return insideTerminal;
0827 }
0828 
0829 #include "moc_kateapp.cpp"