File indexing completed on 2024-04-28 16:29:36

0001 /*
0002     SPDX-FileCopyrightText: 2001 Michael Edwardes <mte@users.sourceforge.net>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 
0007 #include <config-kmymoney.h>
0008 #include <config-kmymoney-version.h>
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 #include <QWidget>
0014 #include <QStringList>
0015 #include <QApplication>
0016 #include <QCommandLineParser>
0017 #include <QSplashScreen>
0018 #include <QStandardPaths>
0019 #include <QRegularExpression>
0020 #include <QRegularExpressionMatch>
0021 #ifdef KMM_DBUS
0022 #include <QDBusConnection>
0023 #include <QDBusConnectionInterface>
0024 #include <QDBusInterface>
0025 #endif
0026 // ----------------------------------------------------------------------------
0027 // KDE Includes
0028 
0029 #include <KTipDialog>
0030 #include <KLocalizedString>
0031 #include <KMessageBox>
0032 #include <Kdelibs4ConfigMigrator>
0033 
0034 // ----------------------------------------------------------------------------
0035 // Project Includes
0036 
0037 #include "mymoney/mymoneyfile.h"
0038 #include "mymoneyexception.h"
0039 #include "kmymoney.h"
0040 #include "kstartuplogo.h"
0041 #include "kcreditswindow.h"
0042 #include "kmymoneyutils.h"
0043 #include "kmymoneysettings.h"
0044 #include "misc/webconnect.h"
0045 #include "platformtools.h"
0046 
0047 #ifdef Q_OS_WIN
0048 #include <windows.h>
0049 #endif
0050 
0051 #ifdef KMM_DEBUG
0052 #include "mymoneyutils.h"
0053 #include "mymoneytracer.h"
0054 #endif
0055 
0056 bool timersOn = false;
0057 
0058 KMyMoneyApp* kmymoney;
0059 
0060 static int runKMyMoney(QApplication& a, std::unique_ptr<QSplashScreen> splash, const QUrl & file, bool noFile);
0061 static void migrateConfigFiles();
0062 
0063 int main(int argc, char *argv[])
0064 {
0065 #ifdef KMM_DEBUG
0066     // make sure the DOM attributes are stored in the same order
0067     // each time a file is saved. Don't use QHash for security
0068     // relevant things from here on. See
0069     // https://doc.qt.io/qt-5/qhash.html#algorithmic-complexity-attacks
0070     // for details.
0071     qSetGlobalQHashSeed(0);
0072 #endif
0073 
0074 #ifdef Q_OS_WIN
0075     // enable console logging on Windows
0076     if (AttachConsole(ATTACH_PARENT_PROCESS)) {
0077         freopen("CONOUT$", "w", stdout);
0078         freopen("CONOUT$", "w", stderr);
0079     }
0080 #endif
0081 
0082     /*
0083      * For AppImages we need to set the LD_LIBRARY_PATH to have
0084      * $APPDIR/usr/lib/ as the first entry. We set this up here.
0085      * For security reasons, we extract the directory from argv[0]
0086      * and don't use APPDIR directly. It would otherwise allow to
0087      * add a different library path for non AppImage versions.
0088      */
0089     if (qEnvironmentVariableIsSet("APPDIR")) {
0090         QByteArray appFullPath(argv[0]);
0091         auto lastDirSeparator = appFullPath.lastIndexOf('/');
0092         auto appDir = appFullPath.left(lastDirSeparator + 1);
0093         auto appName = QString::fromUtf8(appFullPath.mid(lastDirSeparator + 1));
0094         qDebug() << "AppImageInfo:" << appFullPath << appDir << appName;
0095         if (appName == QStringLiteral("AppRun.wrapped")) {
0096             appDir.append("usr/lib");
0097             const auto libPath = qgetenv("LD_LIBRARY_PATH");
0098             auto newLibPath = appDir;
0099             if (!libPath.isEmpty()) {
0100                 newLibPath.append(':');
0101                 newLibPath.append(libPath);
0102             }
0103             qputenv("RUNNING_AS_APPIMAGE", "true");
0104             qputenv("LD_LIBRARY_PATH", newLibPath);
0105             qDebug() << "LD_LIBRARY_PATH set to" << newLibPath;
0106         }
0107     }
0108 
0109     /**
0110      * Create application first
0111      */
0112     QApplication app(argc, argv);
0113     KLocalizedString::setApplicationDomain("kmymoney");
0114 
0115     migrateConfigFiles();
0116 
0117     /**
0118      * construct and register about data
0119      */
0120     KAboutData aboutData(QStringLiteral("kmymoney"), i18n("KMyMoney"), QStringLiteral(VERSION));
0121     aboutData.setOrganizationDomain("kde.org");
0122     KAboutData::setApplicationData(aboutData);
0123 
0124     QStringList fileUrls;
0125     bool isNoCatchOption = false;
0126     bool isNoFileOption = false;
0127 
0128 #ifdef KMM_DEBUG
0129     bool isDumpActionsOption = false;
0130 #endif
0131 
0132     if (argc != 0) {
0133         /**
0134          * Create command line parser and feed it with known options
0135          */
0136         QCommandLineParser parser;
0137         aboutData.setupCommandLine(&parser);
0138 
0139         // language
0140         const QCommandLineOption langOption(QStringLiteral("lang"), i18n("language to be used"), "lang");
0141         parser.addOption(langOption);
0142 
0143         // no file
0144         const QCommandLineOption noFileOption(QStringLiteral("n"), i18n("do not open last used file"));
0145         parser.addOption(noFileOption);
0146 
0147         // timers
0148         const QCommandLineOption timersOption(QStringLiteral("timers"), i18n("enable performance timers"));
0149         parser.addOption(timersOption);
0150 
0151         // no catch
0152         const QCommandLineOption noCatchOption(QStringLiteral("nocatch"), i18n("do not globally catch uncaught exceptions"));
0153         parser.addOption(noCatchOption);
0154 
0155 #ifdef KMM_DEBUG
0156         // The following options are only available when compiled in debug mode
0157         // trace
0158         const QCommandLineOption traceOption(QStringLiteral("trace"), i18n("turn on program traces"));
0159         parser.addOption(traceOption);
0160 
0161         // dump actions
0162         const QCommandLineOption dumpActionsOption(QStringLiteral("dump-actions"), i18n("dump the names of all defined QAction objects to stdout and quit"));
0163         parser.addOption(dumpActionsOption);
0164 #endif
0165 
0166         // INSERT YOUR COMMANDLINE OPTIONS HERE
0167         // url to open
0168         parser.addPositionalArgument(QStringLiteral("url"), i18n("file to open"));
0169 
0170         /**
0171          * do the command line parsing (and handle --help and --version)
0172          */
0173         parser.process(QApplication::arguments());
0174 
0175         if (parser.isSet(QStringLiteral("author")) || parser.isSet(QStringLiteral("license"))) {
0176             aboutData = initializeCreditsData();
0177         }
0178 
0179         if (parser.isSet(QStringLiteral("lang"))) {
0180             QString language = parser.value(langOption);
0181             if (!language.isEmpty()) {
0182                 KLocalizedString::setLanguages(QStringList() << language);
0183             }
0184         }
0185 
0186         /**
0187          * handle standard options
0188          */
0189         aboutData.processCommandLine(&parser);
0190 
0191 #ifdef KMM_DEBUG
0192         if (parser.isSet(traceOption))
0193             MyMoneyTracer::on();
0194         timersOn = parser.isSet(timersOption);
0195         isDumpActionsOption = parser.isSet(dumpActionsOption);
0196 #endif
0197 
0198         isNoCatchOption = parser.isSet(noCatchOption);
0199         isNoFileOption = parser.isSet(noFileOption);
0200         fileUrls = parser.positionalArguments();
0201     }
0202 
0203     // create the singletons before we start memory checking
0204     // to avoid false error reports
0205     auto file = MyMoneyFile::instance();
0206     Q_UNUSED(file)
0207 
0208     KMyMoneyUtils::checkConstants();
0209 
0210     // show startup logo
0211     std::unique_ptr<QSplashScreen> splash(KMyMoneySettings::showSplash() ? createStartupLogo() : nullptr);
0212     app.processEvents();
0213 
0214     // setup the MyMoneyMoney locale settings according to the KDE settings
0215     MyMoneyMoney::setThousandSeparator(QLocale().groupSeparator());
0216     MyMoneyMoney::setDecimalSeparator(QLocale().decimalPoint());
0217 
0218     // setup format of negative values
0219     MyMoneyMoney::setNegativeSpaceSeparatesSymbol(false);
0220     MyMoneyMoney::setNegativePrefixCurrencySymbol(false);
0221     MyMoneyMoney::setNegativeMonetarySignPosition(static_cast<eMyMoney::Money::signPosition>(platformTools::currencySignPosition(true)));
0222     switch(platformTools::currencySymbolPosition(true)) {
0223     case platformTools::AfterQuantityMoneyWithSpace:
0224         MyMoneyMoney::setNegativeSpaceSeparatesSymbol(true);
0225     // intentional fall through
0226     case platformTools::AfterQuantityMoney:
0227         break;
0228 
0229     case platformTools::BeforeQuantityMoneyWithSpace:
0230         MyMoneyMoney::setNegativeSpaceSeparatesSymbol(true);
0231     // intentional fall through
0232     case platformTools::BeforeQuantityMoney:
0233         MyMoneyMoney::setNegativePrefixCurrencySymbol(true);
0234         break;
0235     }
0236     // setup format of positive values
0237     MyMoneyMoney::setPositiveSpaceSeparatesSymbol(false);
0238     MyMoneyMoney::setPositivePrefixCurrencySymbol(false);
0239     MyMoneyMoney::setPositiveMonetarySignPosition(static_cast<eMyMoney::Money::signPosition>(platformTools::currencySignPosition(false)));
0240     switch(platformTools::currencySymbolPosition(false)) {
0241     case platformTools::AfterQuantityMoneyWithSpace:
0242         MyMoneyMoney::setPositiveSpaceSeparatesSymbol(true);
0243     // intentional fall through
0244     case platformTools::AfterQuantityMoney:
0245         break;
0246     case platformTools::BeforeQuantityMoneyWithSpace:
0247         MyMoneyMoney::setPositiveSpaceSeparatesSymbol(true);
0248     // intentional fall through
0249     case platformTools::BeforeQuantityMoney:
0250         MyMoneyMoney::setPositivePrefixCurrencySymbol(true);
0251         break;
0252     }
0253 
0254     kmymoney = new KMyMoneyApp();
0255 
0256 #ifdef KMM_DEBUG
0257     if (isDumpActionsOption) {
0258         kmymoney->dumpActions();
0259 
0260         // Before we delete the application, we make sure that we destroy all
0261         // widgets by running the event loop for some time to catch all those
0262         // widgets that are requested to be destroyed using the deleteLater() method.
0263         //QApplication::eventLoop()->processEvents(QEventLoop::ExcludeUserInput, 10);
0264         QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 10);
0265 
0266         delete kmymoney;
0267         exit(0);
0268     }
0269 #endif
0270 
0271     QString fname;
0272     // in case a filename is provided we need to check if it is a local
0273     // file. In case the name does not start with "file://" or "./" or "/"
0274     // we need to prepend "./" to fake a relative filename. Otherwise, QUrl prepends
0275     // "http://" and uses the full path which will not work.
0276     // On MS-Windows we also need to check if the filename starts with a
0277     // drive letter or the backslash variants.
0278     //
0279     // The handling might be different on other OSes
0280     if (!fileUrls.isEmpty()) {
0281         fname = fileUrls.front();
0282         QFileInfo fi(fname);
0283         auto needLeadIn = fi.isFile();
0284 #ifdef Q_OS_WIN
0285         QRegularExpression exp("^[a-z]:", QRegularExpression::CaseInsensitiveOption);
0286         needLeadIn &= !exp.match(fname).hasMatch() //
0287                       && !fname.startsWith(QLatin1String(".\\")) //
0288                       && !fname.startsWith(QLatin1String("\\"));
0289 #endif
0290         needLeadIn &= !fname.startsWith(QLatin1String("file://")) //
0291                       && !fname.startsWith(QLatin1String("./")) //
0292                       && !fname.startsWith(QLatin1String("/"));
0293         if (needLeadIn) {
0294             fname.prepend(QLatin1String("./"));
0295         }
0296     }
0297 
0298     const QUrl url = QUrl::fromUserInput(fname, QLatin1String("."), QUrl::AssumeLocalFile);
0299     int rc = 0;
0300     if (isNoCatchOption) {
0301         qDebug("Running w/o global try/catch block");
0302         rc = runKMyMoney(app, std::move(splash), url, isNoFileOption);
0303     } else {
0304         try {
0305             rc = runKMyMoney(app, std::move(splash), url, isNoFileOption);
0306         } catch (const MyMoneyException &e) {
0307             KMessageBox::detailedError(0, i18n("Uncaught error. Please report the details to the developers"), QString::fromLatin1(e.what()));
0308             throw;
0309         }
0310     }
0311 
0312     return rc;
0313 }
0314 
0315 int runKMyMoney(QApplication& a, std::unique_ptr<QSplashScreen> splash, const QUrl & file, bool noFile)
0316 {
0317 
0318 #ifdef Q_OS_MAC
0319     kmymoney->setUnifiedTitleAndToolBarOnMac(true);
0320     kmymoney->setAttribute(Qt::WA_TranslucentBackground, true);
0321     kmymoney->setWindowFlag(Qt::MacWindowToolBarButtonHint);
0322 #endif
0323 
0324     bool instantQuit = false;
0325 
0326     /**
0327      * enable high dpi icons
0328      */
0329     a.setAttribute(Qt::AA_UseHighDpiPixmaps);
0330 
0331     if (kmymoney->webConnect()->isClient()) {
0332         // If the user launches a second copy of the app and includes a file to
0333         // open, they are probably attempting a "WebConnect" session.  In this case,
0334         // we'll check to make sure it's an importable file that's passed in, and if so, we'll
0335         // notify the primary instance of the file and kill ourselves.
0336 
0337         if (file.isValid()) {
0338             if (kmymoney->isImportableFile(file)) {
0339                 instantQuit = true;
0340                 kmymoney->webConnect()->loadFile(file);
0341             }
0342         }
0343     }
0344 
0345     kmymoney->centralWidget()->setEnabled(false);
0346 
0347     // force complete paint of widgets
0348     qApp->processEvents();
0349 
0350     if (!instantQuit) {
0351         QString importfile;
0352         QUrl url;
0353         // make sure, we take the file provided on the command
0354         // line before we go and open the last one used
0355         if (file.isValid()) {
0356             // Check to see if this is an importable file, as opposed to a loadable
0357             // file.  If it is importable, what we really want to do is load the
0358             // last used file anyway and then immediately import this file.  This
0359             // implements a "web connect" session where there is not already an
0360             // instance of the program running.
0361 
0362             if (kmymoney->isImportableFile(file)) {
0363                 importfile = file.path();
0364                 url = QUrl::fromUserInput(kmymoney->readLastUsedFile());
0365             } else {
0366                 url = file;
0367             }
0368 
0369         } else {
0370             url = QUrl::fromUserInput(kmymoney->readLastUsedFile());
0371         }
0372 
0373         if (url.isValid() && !noFile) {
0374             if (importfile.isEmpty()) {
0375                 KTipDialog::showTip(kmymoney, QString(), false);
0376             }
0377             kmymoney->slotFileOpenRecent(url);
0378 
0379         } else if (KMyMoneySettings::firstTimeRun()) {
0380             // resetting the splash here is needed for ms-windows to have access
0381             // to the new file wizard
0382             splash.reset();
0383             kmymoney->slotFileNew();
0384         }
0385 
0386         KMyMoneySettings::setFirstTimeRun(false);
0387 
0388         if (!importfile.isEmpty()) {
0389             // resetting the splash here is needed for ms-windows to have access
0390             // to the web connect widgets
0391             splash.reset();
0392             kmymoney->webConnect(importfile, QByteArray());
0393         }
0394 
0395     } else {
0396         // the instantQuit flag is set, so we force the app to quit right away
0397         kmymoney->slotFileQuit();
0398     }
0399 
0400     kmymoney->centralWidget()->setEnabled(true);
0401 
0402     // we cannot call kmymoney->show() directly as this causes a crash
0403     // when running on some non KDE desktops (e.g. XFCE) with QWebEngine
0404     // enabled. Postponing the call until we are inside the event loop
0405     // solved the problem.
0406     QMetaObject::invokeMethod(kmymoney, "show", Qt::QueuedConnection);
0407     splash.reset();
0408 
0409     const int rc = a.exec();      //krazy:exclude=crashy
0410     return rc;
0411 }
0412 
0413 static void migrateConfigFiles()
0414 {
0415     const QString sMainConfigName(QStringLiteral("kmymoneyrc"));
0416     const QString sMainConfigSubdirectory(QStringLiteral("kmymoney/")); // all KMM config files should be in ~/.config/kmymoney/
0417     const QString sMainConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) +
0418                                     QLatin1Char('/') +
0419                                     sMainConfigSubdirectory;
0420 
0421     if (!QFile::exists(sMainConfigPath + sMainConfigName)) { // if main config file doesn't exist, then it's first run
0422 
0423         // it could be migration from KDE4 to KF5 so prepare list of configuration files to migrate
0424         QStringList sConfigNames
0425         {
0426             sMainConfigName,
0427             QStringLiteral("csvimporterrc"),
0428             QStringLiteral("printcheckpluginrc"),
0429             QStringLiteral("icalendarexportpluginrc"),
0430             QStringLiteral("kbankingrc"),
0431         };
0432 
0433         // Copy KDE 4 config files to the KF5 location
0434         Kdelibs4ConfigMigrator migrator(QStringLiteral("kmymoney"));
0435         migrator.setConfigFiles(sConfigNames);
0436         migrator.setUiFiles(QStringList{QStringLiteral("kmymoneyui.rc")});
0437         migrator.migrate();
0438 
0439         QFileInfo fileInfo(sMainConfigPath + sMainConfigName);
0440         QDir().mkpath(fileInfo.absolutePath());
0441         const QString sOldMainConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) +
0442                                            QLatin1Char('/');
0443 
0444         // some files have changed their names during switch to KF5, so prepare map for name replacements
0445         QMap<QString, QString> configNamesChange {
0446             {QStringLiteral("printcheckpluginrc"), QStringLiteral("checkprintingrc")},
0447             {QStringLiteral("icalendarexportpluginrc"), QStringLiteral("icalendarexporterrc")},
0448         };
0449 
0450         for (const auto& sConfigName : qAsConst(sConfigNames)) {
0451             const QString sOldConfigFilename = sOldMainConfigPath + sConfigName;
0452             const QString sNewConfigFilename = sMainConfigPath + configNamesChange.value(sConfigName, sConfigName);
0453             if (QFile::exists(sOldConfigFilename)) {
0454                 if (QFile::copy(sOldConfigFilename, sNewConfigFilename))
0455                     QFile::remove(sOldConfigFilename);
0456             }
0457         }
0458     }
0459     KConfig::setMainConfigName(sMainConfigSubdirectory + sMainConfigName); // otherwise it would be ~/.config/kmymoneyrc and not ~/.config/kmymoney/kmymoneyrc
0460 }