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 }