File indexing completed on 2024-12-01 11:19:44

0001 // clang-format off
0002 /*
0003  *  This file is part of KDiff3.
0004  *
0005  * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de
0006  * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com
0007  * SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 // clang-format on
0010 
0011 #include "kdiff3_shell.h"
0012 #include "TypeUtils.h"
0013 #include "version.h"
0014 
0015 #include <stdio.h>  // for fileno, stderr
0016 #include <stdlib.h> // for exit
0017 
0018 #ifndef Q_OS_WIN
0019 #include <unistd.h>
0020 #endif
0021 
0022 #include <KAboutData>
0023 #include <KCrash>
0024 #include <KLocalizedString>
0025 #include <KMessageBox>
0026 
0027 #include <QApplication>
0028 #include <QCommandLineOption>
0029 #include <QCommandLineParser>
0030 #include <QFile>
0031 #include <QStandardPaths>
0032 #include <QStringList>
0033 #include <QTextStream>
0034 
0035 void initialiseCmdLineArgs(QCommandLineParser* cmdLineParser)
0036 {
0037     const QString configFileName = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, "kdiff3rc");
0038     QFile configFile(configFileName);
0039     QString ignorableOptionsLine = "-u;-query;-html;-abort";
0040     if(configFile.open(QIODevice::ReadOnly))
0041     {
0042         QTextStream ts(&configFile);
0043         while(!ts.atEnd())
0044         {
0045             const QString line = ts.readLine();
0046             if(line.startsWith(u8"IgnorableCmdLineOptions="))
0047             {
0048                 const QtSizeType pos = line.indexOf('=');
0049                 if(pos >= 0)
0050                 {
0051                     ignorableOptionsLine = line.mid(pos + 1);
0052                 }
0053                 break;
0054             }
0055         }
0056     }
0057 
0058     const QStringList ignorableOptions = ignorableOptionsLine.split(';');
0059 
0060     for(QString ignorableOption: ignorableOptions)
0061     {
0062         ignorableOption.remove('-');
0063         if(!ignorableOption.isEmpty())
0064         {
0065             if(ignorableOption.length() == 1)
0066             {
0067                 cmdLineParser->addOption(QCommandLineOption({ignorableOption, u8"ignore"}, i18n("Ignored. (User defined.)")));
0068             }
0069             else
0070             {
0071                 cmdLineParser->addOption(QCommandLineOption(ignorableOption, i18n("Ignored. (User defined.)")));
0072             }
0073         }
0074     }
0075 }
0076 
0077 qint32 main(qint32 argc, char* argv[])
0078 {
0079     constexpr QLatin1String appName("kdiff3", sizeof("kdiff3") - 1);
0080     //Syncronize qt HDPI behavoir on all versions/platforms
0081 #if(QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
0082     QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
0083     QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
0084 #endif
0085     QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
0086 
0087     QApplication app(argc, argv); // KAboutData and QCommandLineParser depend on this being setup.
0088     KLocalizedString::setApplicationDomain(appName.data());
0089 
0090     KCrash::initialize();
0091 
0092     const QString i18nName = i18n("KDiff3");
0093     QString appVersion(KDIFF3_VERSION_STRING);
0094 
0095     appVersion += i18nc("Program version info.", " (64 bit)");
0096 
0097     const QString description = i18n("Tool for Comparison and Merge of Files and Folders");
0098     const QString copyright = i18n("(c) 2002-2014 Joachim Eibl, (c) 2017 Michael Reeves KF5/Qt5 port");
0099     const QString homePage = QStringLiteral("https://kde.org/applications/development/kdiff3");
0100 
0101     KAboutData aboutData(appName, i18nName,
0102                          appVersion, description, KAboutLicense::GPL_V2, copyright, QString(),
0103                          homePage);
0104 
0105     KAboutData::setApplicationData(aboutData);
0106 
0107     /*
0108         The QCommandLineParser is a static scoped unique ptr. This is safe given that.
0109         As the distuctor will not be fired until main exits.
0110     */
0111     QCommandLineParser* cmdLineParser = KDiff3Shell::getParser().get();
0112     cmdLineParser->setApplicationDescription(aboutData.shortDescription());
0113 
0114     aboutData.setupCommandLine(cmdLineParser);
0115 
0116     initialiseCmdLineArgs(cmdLineParser);
0117     // ignorable command options
0118     cmdLineParser->addOption(QCommandLineOption({u8"m", u8"merge"}, i18n("Merge the input.")));
0119     cmdLineParser->addOption(QCommandLineOption({u8"b", u8"base"}, i18n("Explicit base file. For compatibility with certain tools."), u8"file"));
0120     cmdLineParser->addOption(QCommandLineOption({u8"o", u8"output"}, i18n("Output file. Implies -m. E.g.: -o newfile.txt"), u8"file"));
0121     cmdLineParser->addOption(QCommandLineOption(u8"out", i18n("Output file, again. (For compatibility with certain tools.)"), u8"file"));
0122 #ifdef ENABLE_AUTO
0123     cmdLineParser->addOption(QCommandLineOption(u8"auto", i18n("No GUI if all conflicts are auto-solvable. (Needs -o file)")));
0124     cmdLineParser->addOption(QCommandLineOption(u8"noauto", i18n("Ignore --auto and always show GUI.")));
0125 #else
0126     cmdLineParser->addOption(QCommandLineOption(u8"noauto", i18n("Ignored.")));
0127     cmdLineParser->addOption(QCommandLineOption(u8"auto", i18n("Ignored.")));
0128 #endif
0129     cmdLineParser->addOption(QCommandLineOption(u8"L1", i18n("Visible name replacement for input file 1 (base)."), u8"alias1"));
0130     cmdLineParser->addOption(QCommandLineOption(u8"L2", i18n("Visible name replacement for input file 2."), u8"alias2"));
0131     cmdLineParser->addOption(QCommandLineOption(u8"L3", i18n("Visible name replacement for input file 3."), u8"alias3"));
0132     cmdLineParser->addOption(QCommandLineOption({u8"L", u8"fname"}, i18n("Alternative visible name replacement. Supply this once for every input."), u8"alias"));
0133     cmdLineParser->addOption(QCommandLineOption(u8"cs", i18n("Override a config setting. Use once for every setting. E.g.: --cs \"AutoAdvance=1\""), u8"string"));
0134     cmdLineParser->addOption(QCommandLineOption(u8"confighelp", i18n("Show list of config settings and current values.")));
0135     cmdLineParser->addOption(QCommandLineOption(u8"config", i18n("Use a different config file."), u8"file"));
0136 
0137     // other command options
0138     cmdLineParser->addPositionalArgument(u8"[File1]", i18n("file1 to open (base, if not specified via --base)"));
0139     cmdLineParser->addPositionalArgument(u8"[File2]", i18n("file2 to open"));
0140     cmdLineParser->addPositionalArgument(u8"[File3]", i18n("file3 to open"));
0141 
0142     bool isAtty = true;
0143 
0144 #ifndef Q_OS_WIN
0145     isAtty = isatty(fileno(stderr)) == 1;//will be true for redirected output as well
0146 #endif
0147     /*
0148         QCommandLineParser::process does what is expected on windows or when running from a commandline.
0149         However, it only accounts for a lack of terminal output on windows.
0150     */
0151     if(isAtty)
0152     {
0153         cmdLineParser->process(QCoreApplication::arguments());
0154     }
0155     else
0156     {
0157         /*
0158             There is no terminal connected so don't just exit mysteriously on error.
0159         */
0160         if(!cmdLineParser->parse(QCoreApplication::arguments()))
0161         {
0162             const QString errorMessage = cmdLineParser->errorText();
0163 
0164             KMessageBox::error(nullptr, "<html><head/><body><h2>" + errorMessage + "</h2><pre>" + i18n("See kdiff3 --help for supported options.") + "</pre></body></html>", aboutData.displayName());
0165             exit(1);
0166         }
0167 
0168         if(cmdLineParser->isSet(QStringLiteral("version")))
0169         {
0170             KMessageBox::information(nullptr,
0171                                     aboutData.displayName() + ' ' + aboutData.version(), aboutData.displayName());
0172             exit(0);
0173         }
0174         if(cmdLineParser->isSet(QStringLiteral("help")))
0175         {
0176             KMessageBox::information(nullptr, "<html><head/><body><pre>" + cmdLineParser->helpText() + "</pre></body></html>", aboutData.displayName());
0177 
0178             exit(0);
0179         }
0180     }
0181 
0182     aboutData.processCommandLine(cmdLineParser);
0183 
0184     /*
0185         This short segment is wrapped in a lambda to delay KDiff3Shell construction until
0186         after the main event loop starts. Thus allowing us to avoid std::exit as much as
0187         possiable. Makes for a cleaner exit.
0188     */
0189     QPointer<KDiff3Shell> p;
0190     QMetaObject::invokeMethod(
0191         qApp, [&p] {
0192             /*
0193               Do not attempt to call show here that will be done later.
0194               This variable exists solely to insure the KDiff3Shell is deleted on exit.
0195             */
0196             p = new KDiff3Shell();
0197         },
0198         Qt::QueuedConnection);
0199     qint32 retVal = QApplication::exec();
0200     delete p;
0201     return retVal;
0202 }