File indexing completed on 2024-04-21 05:51:23

0001 /*
0002     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // To time the creation and total launch time (i. e. until window is
0008 // visible/responsive):
0009 // #define PROFILE_STARTUP
0010 
0011 // Own
0012 #include "Application.h"
0013 #include "KonsoleSettings.h"
0014 #include "MainWindow.h"
0015 #include "ViewManager.h"
0016 #include "config-konsole.h"
0017 #include "widgets/ViewContainer.h"
0018 
0019 // OS specific
0020 #include <QApplication>
0021 #include <QCommandLineParser>
0022 #include <QDir>
0023 #include <QProxyStyle>
0024 #include <QStandardPaths>
0025 #include <qplatformdefs.h>
0026 
0027 // KDE
0028 #include <KAboutData>
0029 #include <KCrash>
0030 #include <KLocalizedString>
0031 #include <kconfigwidgets_version.h>
0032 #include <kdbusservice.h>
0033 
0034 using Konsole::Application;
0035 
0036 #ifdef PROFILE_STARTUP
0037 #include <QDebug>
0038 #include <QElapsedTimer>
0039 #include <QTimer>
0040 #endif
0041 
0042 // fill the KAboutData structure with information about contributors to Konsole.
0043 void fillAboutData(KAboutData &aboutData);
0044 
0045 // check and report whether this konsole instance should use a new konsole
0046 // process, or re-use an existing konsole process.
0047 bool shouldUseNewProcess(int argc, char *argv[]);
0048 
0049 // restore sessions saved by KDE.
0050 void restoreSession(Application &app);
0051 
0052 // Workaround for a bug in KDBusService: https://bugs.kde.org/show_bug.cgi?id=355545
0053 // It calls exit(), but the program can't exit before the QApplication is deleted:
0054 // https://bugreports.qt.io/browse/QTBUG-48709
0055 static bool needToDeleteQApplication = false;
0056 void deleteQApplication()
0057 {
0058     if (needToDeleteQApplication) {
0059         delete qApp;
0060     }
0061 }
0062 
0063 // This override resolves following problem: since some qt version if
0064 // XDG_CURRENT_DESKTOP ≠ kde, then pressing and immediately releasing Alt
0065 // key makes focus get stuck in QMenu.
0066 // Upstream report: https://bugreports.qt.io/browse/QTBUG-77355
0067 class MenuStyle : public QProxyStyle
0068 {
0069 public:
0070     int styleHint(const StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const override
0071     {
0072         return (stylehint == QStyle::SH_MenuBar_AltKeyNavigation) ? 0 : QProxyStyle::styleHint(stylehint, opt, widget, returnData);
0073     }
0074 };
0075 
0076 // Used to control migrating config entries.
0077 // Increment when there are new keys to migrate.
0078 static int CurrentConfigVersion = 1;
0079 
0080 static void migrateRenamedConfigKeys()
0081 {
0082     KSharedConfigPtr konsoleConfig = KSharedConfig::openConfig(QStringLiteral("konsolerc"));
0083     KConfigGroup verGroup = konsoleConfig->group(QStringLiteral("General"));
0084     const int savedVersion = verGroup.readEntry<int>("ConfigVersion", 0);
0085     if (savedVersion < CurrentConfigVersion) {
0086         struct KeyInfo {
0087             const char *groupName;
0088             const char *oldKeyName;
0089             const char *newKeyName;
0090         };
0091 
0092         static const KeyInfo keys[] = {{"KonsoleWindow", "SaveGeometryOnExit", "RememberWindowSize"}};
0093 
0094         // Migrate renamed config keys
0095         for (const auto &[group, oldName, newName] : keys) {
0096             KConfigGroup cg = konsoleConfig->group(QLatin1String(group));
0097             if (cg.exists() && cg.hasKey(oldName)) {
0098                 const bool value = cg.readEntry(oldName, false);
0099                 cg.deleteEntry(oldName);
0100                 cg.writeEntry(newName, value);
0101             }
0102         }
0103 
0104         // With 5.93 KColorSchemeManager from KConfigWidgets, handles the loading
0105         // and saving of the widget color scheme, and uses "ColorScheme" as the
0106         // entry name, so clean-up here
0107         KConfigGroup cg(konsoleConfig, QStringLiteral("UiSettings"));
0108         const QString schemeName = cg.readEntry("WindowColorScheme");
0109         cg.deleteEntry("WindowColorScheme");
0110         cg.writeEntry("ColorScheme", schemeName);
0111 
0112         verGroup.writeEntry("ConfigVersion", CurrentConfigVersion);
0113         konsoleConfig->sync();
0114     }
0115 }
0116 
0117 // ***
0118 // Entry point into the Konsole terminal application.
0119 // ***
0120 int main(int argc, char *argv[])
0121 {
0122 #ifdef PROFILE_STARTUP
0123     QElapsedTimer timer;
0124     timer.start();
0125 #endif
0126 
0127     /**
0128      * enable dark mode for title bar on Windows
0129      */
0130 #if defined(Q_OS_WIN)
0131     if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) {
0132         qputenv("QT_QPA_PLATFORM", "windows:darkmode=1");
0133     }
0134 #endif
0135 
0136     // Check if any of the arguments makes it impossible to re-use an existing process.
0137     // We need to do this manually and before creating a QApplication, because
0138     // QApplication takes/removes the Qt specific arguments that are incompatible.
0139     const bool needNewProcess = shouldUseNewProcess(argc, argv);
0140     if (!needNewProcess) { // We need to avoid crashing
0141         needToDeleteQApplication = true;
0142     }
0143 
0144     auto app = new QApplication(argc, argv);
0145     /**
0146      * For Windows: use Breeze if available
0147      * Of all tested styles that works the best for us
0148      * NOTE: Taken from Kate
0149      */
0150 #if defined(Q_OS_WIN)
0151     QApplication::setStyle(QStringLiteral("breeze"));
0152 #else
0153     app->setStyle(new MenuStyle());
0154 #endif
0155 
0156     migrateRenamedConfigKeys();
0157 
0158     app->setWindowIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal")));
0159 
0160     KLocalizedString::setApplicationDomain("konsole");
0161 
0162     KAboutData about(QStringLiteral("konsole"),
0163                      i18nc("@title", "Konsole"),
0164                      QStringLiteral(KONSOLE_VERSION),
0165                      i18nc("@title", "Terminal emulator"),
0166                      KAboutLicense::GPL_V2,
0167                      i18nc("@info:credit", "(c) 1997-2022, The Konsole Developers"),
0168                      QString(),
0169                      QStringLiteral("https://konsole.kde.org/"));
0170     fillAboutData(about);
0171 
0172     KAboutData::setApplicationData(about);
0173 
0174     KCrash::initialize();
0175 
0176     QSharedPointer<QCommandLineParser> parser(new QCommandLineParser);
0177     parser->setApplicationDescription(about.shortDescription());
0178     about.setupCommandLine(parser.data());
0179 
0180     QStringList args = app->arguments();
0181     QStringList customCommand = Application::getCustomCommand(args);
0182 
0183     Application::populateCommandLineParser(parser.data());
0184 
0185     parser->process(args);
0186     about.processCommandLine(parser.data());
0187 
0188     /// ! DON'T TOUCH THIS ! ///
0189     const KDBusService::StartupOption startupOption =
0190         Konsole::KonsoleSettings::useSingleInstance() && !needNewProcess ? KDBusService::Unique : KDBusService::Multiple;
0191     /// ! DON'T TOUCH THIS ! ///
0192     // If you need to change something here, add your logic _at the bottom_ of
0193     // shouldUseNewProcess(), after reading the explanations there for why you
0194     // probably shouldn't.
0195 
0196     atexit(deleteQApplication);
0197     // Ensure that we only launch a new instance if we need to
0198     // If there is already an instance running, we will quit here
0199     KDBusService dbusService(startupOption | KDBusService::NoExitOnFailure);
0200 
0201     needToDeleteQApplication = false;
0202 
0203     // If we reach this location, there was no existing copy of Konsole
0204     // running, so create a new instance.
0205     Application konsoleApp(parser, customCommand);
0206 
0207     // The activateRequested() signal is emitted when a second instance
0208     // of Konsole is started.
0209     QObject::connect(&dbusService, &KDBusService::activateRequested, &konsoleApp, &Application::slotActivateRequested);
0210 
0211     if (app->isSessionRestored()) {
0212         restoreSession(konsoleApp);
0213     } else {
0214         // Do not finish starting Konsole due to:
0215         // 1. An argument was given to just printed info
0216         // 2. An invalid situation occurred
0217         const bool continueStarting = (konsoleApp.newInstance() != 0);
0218         if (!continueStarting) {
0219             delete app;
0220             return 0;
0221         }
0222     }
0223 
0224 #ifdef PROFILE_STARTUP
0225     qDebug() << "Construction completed in" << timer.elapsed() << "ms";
0226     QTimer::singleShot(0, [&timer]() {
0227         qDebug() << "Startup complete in" << timer.elapsed() << "ms";
0228     });
0229 #endif
0230 
0231     // Since we've allocated the QApplication on the heap for the KDBusService workaround,
0232     // we need to delete it manually before returning from main().
0233     int ret = app->exec();
0234     delete app;
0235     return ret;
0236 }
0237 
0238 bool shouldUseNewProcess(int argc, char *argv[])
0239 {
0240     // The "unique process" model of konsole is incompatible with some or all
0241     // Qt/KDE options. When those incompatible options are given, konsole must
0242     // use new process
0243     //
0244     // TODO: make sure the existing list is OK and add more incompatible options.
0245 
0246     // We need to manually parse the arguments because QApplication removes the
0247     // Qt specific arguments (like --reverse)
0248     QStringList arguments;
0249     arguments.reserve(argc);
0250     for (int i = 0; i < argc; i++) {
0251         arguments.append(QString::fromLocal8Bit(argv[i]));
0252     }
0253 
0254     if (arguments.contains(QLatin1String("--force-reuse"))) {
0255         return false;
0256     }
0257 
0258     // take Qt options into consideration
0259     QStringList qtProblematicOptions;
0260     qtProblematicOptions << QStringLiteral("--session") << QStringLiteral("--name") << QStringLiteral("--reverse") << QStringLiteral("--stylesheet")
0261                          << QStringLiteral("--graphicssystem");
0262 #if HAVE_X11
0263     qtProblematicOptions << QStringLiteral("--display") << QStringLiteral("--visual");
0264 #endif
0265     for (const QString &option : std::as_const(qtProblematicOptions)) {
0266         if (arguments.contains(option)) {
0267             return true;
0268         }
0269     }
0270 
0271     // take KDE options into consideration
0272     QStringList kdeProblematicOptions;
0273     kdeProblematicOptions << QStringLiteral("--config") << QStringLiteral("--style");
0274 #if HAVE_X11
0275     kdeProblematicOptions << QStringLiteral("--waitforwm");
0276 #endif
0277 
0278     for (const QString &option : std::as_const(kdeProblematicOptions)) {
0279         if (arguments.contains(option)) {
0280             return true;
0281         }
0282     }
0283 
0284     // if users have explicitly requested starting a new process
0285     // Support --nofork to retain argument compatibility with older
0286     // versions.
0287     if (arguments.contains(QStringLiteral("--separate")) || arguments.contains(QStringLiteral("--nofork"))) {
0288         return true;
0289     }
0290 
0291     // the only way to create new tab is to reuse existing Konsole process.
0292     if (arguments.contains(QStringLiteral("--new-tab"))) {
0293         return false;
0294     }
0295 
0296     // when starting Konsole from a terminal, a new process must be used
0297     // so that the current environment is propagated into the shells of the new
0298     // Konsole and any debug output or warnings from Konsole are written to
0299     // the current terminal
0300     bool hasControllingTTY = false;
0301     const int fd = QT_OPEN("/dev/tty", O_RDONLY);
0302     if (fd != -1) {
0303         hasControllingTTY = true;
0304         close(fd);
0305     }
0306 
0307     return hasControllingTTY;
0308 }
0309 
0310 void fillAboutData(KAboutData &aboutData)
0311 {
0312     aboutData.setOrganizationDomain("kde.org");
0313 
0314     aboutData.addAuthor(i18nc("@info:credit", "Kurt Hindenburg"),
0315                         i18nc("@info:credit",
0316                               "General maintainer, bug fixes and general"
0317                               " improvements"),
0318                         QStringLiteral("kurt.hindenburg@gmail.com"));
0319     aboutData.addAuthor(i18nc("@info:credit", "Robert Knight"),
0320                         i18nc("@info:credit", "Previous maintainer, ported to KDE4"),
0321                         QStringLiteral("robertknight@gmail.com"));
0322     aboutData.addAuthor(i18nc("@info:credit", "Lars Doelle"), i18nc("@info:credit", "Original author"), QStringLiteral("lars.doelle@on-line.de"));
0323     aboutData.addCredit(i18nc("@info:credit", "Ahmad Samir"),
0324                         i18nc("@info:credit", "Major refactoring, bug fixes and major improvements"),
0325                         QStringLiteral("a.samirh78@gmail.com"));
0326     aboutData.addCredit(i18nc("@info:credit", "Carlos Alves"),
0327                         i18nc("@info:credit", "Major refactoring, bug fixes and major improvements"),
0328                         QStringLiteral("cbc.alves@gmail.com"));
0329     aboutData.addCredit(i18nc("@info:credit", "Tomaz Canabrava"),
0330                         i18nc("@info:credit", "Major refactoring, bug fixes and major improvements"),
0331                         QStringLiteral("tcanabrava@kde.org"));
0332     aboutData.addCredit(i18nc("@info:credit", "Gustavo Carneiro"),
0333                         i18nc("@info:credit", "Major refactoring, bug fixes and major improvements"),
0334                         QStringLiteral("gcarneiroa@hotmail.com"));
0335     aboutData.addCredit(i18nc("@info:credit", "Edwin Pujols"),
0336                         i18nc("@info:credit", "Bug fixes and general improvements"),
0337                         QStringLiteral("edwin.pujols5@outlook.com"));
0338     aboutData.addCredit(i18nc("@info:credit", "Martin T. H. Sandsmark"),
0339                         i18nc("@info:credit", "Bug fixes and general improvements"),
0340                         QStringLiteral("martin.sandsmark@kde.org"));
0341     aboutData.addCredit(i18nc("@info:credit", "Nate Graham"), i18nc("@info:credit", "Bug fixes and general improvements"), QStringLiteral("nate@kde.org"));
0342     aboutData.addCredit(i18nc("@info:credit", "Mariusz Glebocki"),
0343                         i18nc("@info:credit", "Bug fixes and major improvements"),
0344                         QStringLiteral("mglb@arccos-1.net"));
0345     aboutData.addCredit(i18nc("@info:credit", "Thomas Surrel"),
0346                         i18nc("@info:credit", "Bug fixes and general improvements"),
0347                         QStringLiteral("thomas.surrel@protonmail.com"));
0348     aboutData.addCredit(i18nc("@info:credit", "Jekyll Wu"), i18nc("@info:credit", "Bug fixes and general improvements"), QStringLiteral("adaptee@gmail.com"));
0349     aboutData.addCredit(i18nc("@info:credit", "Waldo Bastian"), i18nc("@info:credit", "Bug fixes and general improvements"), QStringLiteral("bastian@kde.org"));
0350     aboutData.addCredit(i18nc("@info:credit", "Stephan Binner"), i18nc("@info:credit", "Bug fixes and general improvements"), QStringLiteral("binner@kde.org"));
0351     aboutData.addCredit(i18nc("@info:credit", "Thomas Dreibholz"), i18nc("@info:credit", "General improvements"), QStringLiteral("dreibh@iem.uni-due.de"));
0352     aboutData.addCredit(i18nc("@info:credit", "Chris Machemer"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("machey@ceinetworks.com"));
0353     aboutData.addCredit(i18nc("@info:credit", "Francesco Cecconi"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("francesco.cecconi@gmail.com"));
0354     aboutData.addCredit(i18nc("@info:credit", "Stephan Kulow"), i18nc("@info:credit", "Solaris support and history"), QStringLiteral("coolo@kde.org"));
0355     aboutData.addCredit(i18nc("@info:credit", "Alexander Neundorf"),
0356                         i18nc("@info:credit", "Bug fixes and improved startup performance"),
0357                         QStringLiteral("neundorf@kde.org"));
0358     aboutData.addCredit(i18nc("@info:credit", "Peter Silva"), i18nc("@info:credit", "Marking improvements"), QStringLiteral("Peter.A.Silva@gmail.com"));
0359     aboutData.addCredit(i18nc("@info:credit", "Lotzi Boloni"),
0360                         i18nc("@info:credit",
0361                               "Embedded Konsole\n"
0362                               "Toolbar and session names"),
0363                         QStringLiteral("boloni@cs.purdue.edu"));
0364     aboutData.addCredit(i18nc("@info:credit", "David Faure"),
0365                         i18nc("@info:credit",
0366                               "Embedded Konsole\n"
0367                               "General improvements"),
0368                         QStringLiteral("faure@kde.org"));
0369     aboutData.addCredit(i18nc("@info:credit", "Antonio Larrosa"), i18nc("@info:credit", "Visual effects"), QStringLiteral("larrosa@kde.org"));
0370     aboutData.addCredit(i18nc("@info:credit", "Matthias Ettrich"),
0371                         i18nc("@info:credit",
0372                               "Code from the kvt project\n"
0373                               "General improvements"),
0374                         QStringLiteral("ettrich@kde.org"));
0375     aboutData.addCredit(i18nc("@info:credit", "Warwick Allison"),
0376                         i18nc("@info:credit", "Schema and text selection improvements"),
0377                         QStringLiteral("warwick@troll.no"));
0378     aboutData.addCredit(i18nc("@info:credit", "Dan Pilone"), i18nc("@info:credit", "SGI port"), QStringLiteral("pilone@slac.com"));
0379     aboutData.addCredit(i18nc("@info:credit", "Kevin Street"), i18nc("@info:credit", "FreeBSD port"), QStringLiteral("street@iname.com"));
0380     aboutData.addCredit(i18nc("@info:credit", "Sven Fischer"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("herpes@kawo2.renditionwth-aachen.de"));
0381     aboutData.addCredit(i18nc("@info:credit", "Dale M. Flaven"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("dflaven@netport.com"));
0382     aboutData.addCredit(i18nc("@info:credit", "Martin Jones"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("mjones@powerup.com.au"));
0383     aboutData.addCredit(i18nc("@info:credit", "Lars Knoll"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("knoll@mpi-hd.mpg.de"));
0384     aboutData.addCredit(i18nc("@info:credit", "Thanks to many others.\n"));
0385 }
0386 
0387 void restoreSession(Application &app)
0388 {
0389     int n = 1;
0390 
0391     while (KMainWindow::canBeRestored(n)) {
0392         auto mainWindow = app.newMainWindow();
0393         mainWindow->restore(n++);
0394         mainWindow->viewManager()->toggleActionsBasedOnState();
0395         mainWindow->show();
0396 
0397         // TODO: HACK without the code below the sessions would be `uninitialized`
0398         // and the tabs wouldn't display the correct information.
0399         auto tabbedContainer = qobject_cast<Konsole::TabbedViewContainer *>(mainWindow->centralWidget());
0400         for (int i = 0; i < tabbedContainer->count(); i++) {
0401             tabbedContainer->setCurrentIndex(i);
0402         }
0403     }
0404 }