File indexing completed on 2024-05-19 05:43:40

0001 #include "autogen/cutehmi.metadata.hpp"
0002 #include "autogen/cutehmi.dirs.hpp"
0003 #include "cutehmi/daemon/logging.hpp"
0004 #include "cutehmi/daemon/Daemon.hpp"
0005 #include "cutehmi/daemon/CoreData.hpp"
0006 #include "cutehmi/daemon/Exception.hpp"
0007 
0008 #include <cutehmi/Messenger.hpp>
0009 #include <cutehmi/Singleton.hpp>
0010 #include <cutehmi/Internationalizer.hpp>
0011 
0012 #include <QCoreApplication>
0013 #include <QQmlApplicationEngine>
0014 #include <QDir>
0015 #include <QCommandLineParser>
0016 #include <QUrl>
0017 #include <QLibraryInfo>
0018 #include <QFile>
0019 #include <QtGlobal>
0020 #include <QQmlContext>
0021 
0022 using namespace cutehmi::daemon;
0023 
0024 /**
0025  * Main function.
0026  * @param argc number of arguments passed to the program.
0027  * @param argv list of arguments passed to the program.
0028  * @return return code.
0029  */
0030 int main(int argc, char * argv[])
0031 {
0032 #ifdef CUTEHMI_DAEMON_DEFAULT_INIT
0033     static constexpr const char * DEFAULT_INIT = CUTEHMI_DAEMON_DEFAULT_INIT;
0034 #else
0035     static constexpr const char * DEFAULT_INIT = "qrc:/qml/ExtensionLoader.qml";
0036 #endif
0037 
0038 #ifdef CUTEHMI_DAEMON_DEFAULT_EXTENSION
0039     static constexpr const char * DEFAULT_EXTENSION = CUTEHMI_DAEMON_DEFAULT_EXTENSION;
0040 #else
0041     static constexpr const char * DEFAULT_EXTENSION = "";
0042 #endif
0043 
0044 #ifdef CUTEHMI_DAEMON_DEFAULT_COMPONENT
0045     static constexpr const char * DEFAULT_COMPONENT = CUTEHMI_DAEMON_DEFAULT_COMPONENT;
0046 #else
0047     static constexpr const char * DEFAULT_COMPONENT = "Daemon";
0048 #endif
0049 
0050 #ifdef CUTEHMI_DAEMON_DEFAULT_MINOR
0051     static constexpr const char * DEFAULT_MINOR = CUTEHMI_DAEMON_DEFAULT_MINOR;
0052 #else
0053     static constexpr const char * DEFAULT_MINOR = "0";
0054 #endif
0055 
0056     static constexpr const char * DEFAULT_FORKS = "2";
0057 
0058     //<cutehmi.daemon-silent_initialization.principle>
0059     // Output shall remain silent until daemon logging is set up.
0060 
0061     //<Qt-Qt_5_7_0_Reference_Documentation-Threads_and_QObjects-QObject_Reentrancy-creating_QObjects_before_QApplication.assumption>
0062     // "In general, creating QObjects before the QApplication is not supported and can lead to weird crashes on exit, depending on the
0063     //  platform. This means static instances of QObject are also not supported. A properly structured single or multi-threaded application
0064     //  should make the QApplication be the first created, and last destroyed QObject."
0065 
0066     // Set up application.
0067 
0068     QCoreApplication::setOrganizationName(CUTEHMI_DAEMON_VENDOR);
0069     QCoreApplication::setOrganizationDomain(CUTEHMI_DAEMON_DOMAIN);
0070     QCoreApplication::setApplicationName(CUTEHMI_DAEMON_FRIENDLY_NAME);
0071     QCoreApplication::setApplicationVersion(CUTEHMI_DAEMON_VERSION);
0072 
0073     QCoreApplication app(argc, argv);
0074 
0075 
0076     // Initial language setup.
0077 
0078     QString language = QLocale::system().name();
0079 #ifdef CUTEHMI_DAEMON_DEFAULT_LANGUAGE
0080     language = CUTEHMI_DAEMON_DEFAULT_LANGUAGE;
0081 #endif
0082     if (!qEnvironmentVariableIsEmpty("CUTEHMI_LANGUAGE"))
0083         language = qgetenv("CUTEHMI_LANGUAGE");
0084 
0085 
0086     // Configure command line parser and process arguments.
0087 
0088     QCommandLineParser cmd;
0089     cmd.setApplicationDescription(CUTEHMI_DAEMON_TRANSLATED_FRIENDLY_NAME + "\n" + CUTEHMI_DAEMON_TRANSLATED_DESCRIPTION);
0090     cmd.addHelpOption();
0091     cmd.addVersionOption();
0092     CoreData::Options opt {
0093         QStringList({"extension", QCoreApplication::translate("main", "Extension to import."), "[extension]"}),
0094         QStringList({"component", QCoreApplication::translate("main", "Component to create. Defaults to '%1'.").arg(DEFAULT_COMPONENT), "[component]"}),
0095         QCommandLineOption("app", QCoreApplication::translate("main", "Run project in application mode.")),
0096         QCommandLineOption("basedir", QCoreApplication::translate("main", "Set base directory to <dir>."), QCoreApplication::translate("main", "dir")),
0097         QCommandLineOption("init", QCoreApplication::translate("main", "Override loader by specifying initial QML <file> to load."), QCoreApplication::translate("main", "file")),
0098         QCommandLineOption({"m", "minor"}, QCoreApplication::translate("main", "Use <version> for extension minor version to import."), QCoreApplication::translate("main", "version")),
0099         QCommandLineOption("lang", QCoreApplication::translate("main", "Choose application <language>."), QCoreApplication::translate("main", "language")),
0100         QCommandLineOption("pidfile", QCoreApplication::translate("main", "PID file <path> (Unix-specific)."), QCoreApplication::translate("main", "path")),
0101         QCommandLineOption("forks", QCoreApplication::translate("main", "Denotes <number> of forks the daemon should perform (Unix-specific)."), QCoreApplication::translate("main", "number")),
0102     };
0103     opt.init.setDefaultValue(DEFAULT_INIT);
0104     opt.minor.setDefaultValue(DEFAULT_MINOR);
0105     opt.lang.setDefaultValue(language);
0106     opt.pidfile.setDefaultValue(QString("/var/run/") + CUTEHMI_DAEMON_NAME ".pid");
0107     opt.pidfile.setDescription(opt.pidfile.description() + "\nDefault value: '" + opt.pidfile.defaultValues().at(0) + "'.");
0108     opt.basedir.setDefaultValue(QDir(QCoreApplication::applicationDirPath() + "/..").canonicalPath());
0109     opt.basedir.setDescription(opt.basedir.description() + "\nDefault value: '" + opt.basedir.defaultValues().at(0) + "'.");
0110     opt.forks.setDefaultValue(DEFAULT_FORKS);
0111     opt.forks.setDescription(opt.forks.description() + "\nDefault value: '" + opt.forks.defaultValues().at(0) + "'.");
0112     cmd.addOption(opt.app);
0113     cmd.addOption(opt.basedir);
0114     cmd.addOption(opt.init);
0115     cmd.addOption(opt.minor);
0116     cmd.addOption(opt.lang);
0117     cmd.addOption(opt.pidfile);
0118     cmd.addOption(opt.forks);
0119 #ifdef CUTEHMI_DAEMON_FORCE_DEFAULT_OPTIONS
0120     opt.init.setFlags(QCommandLineOption::HiddenFromHelp);
0121     opt.minor.setFlags(QCommandLineOption::HiddenFromHelp);
0122 #else
0123     cmd.addPositionalArgument(opt.extension.at(0), opt.extension.at(1), opt.extension.at(2));
0124     cmd.addPositionalArgument(opt.component.at(0), opt.component.at(1), opt.component.at(2));
0125 #endif
0126     cmd.process(app);
0127 
0128 
0129     // Prepare program core.
0130 
0131     CoreData coreData;
0132     coreData.app = & app;
0133     coreData.cmd = & cmd;
0134     coreData.opt = & opt;
0135     coreData.language = language;
0136 
0137     std::function<int(CoreData &)> core = [](CoreData & data) {
0138         try {
0139             CUTEHMI_DEBUG("Default locale: " << QLocale());
0140 
0141             if (!qgetenv("CUTEHMI_LANGUAGE").isEmpty())
0142                 CUTEHMI_DEBUG("Default language set by 'CUTEHMI_LANGUAGE' environmental variable: " << qgetenv("CUTEHMI_LANGUAGE"));
0143 
0144             if (data.cmd->isSet(data.opt->lang))
0145                 data.language = data.cmd->value(data.opt->lang);
0146 
0147             CUTEHMI_DEBUG("Language: " << data.language);
0148             cutehmi::Internationalizer::Instance().setUILanguage(data.language);
0149             cutehmi::Internationalizer::Instance().loadQtTranslation();
0150             cutehmi::Internationalizer::Instance().loadTranslation(CUTEHMI_DAEMON_NAME);
0151 
0152             QDir baseDir = data.cmd->value(data.opt->basedir);
0153             QString baseDirPath = baseDir.absolutePath() + "/";
0154             CUTEHMI_DEBUG("Base directory: " << baseDirPath);
0155 
0156             QString extensionsDirPath = QDir::cleanPath(QCoreApplication::applicationDirPath() + "/" + QDir("/" CUTEHMI_DIRS_TOOLS_INSTALL_SUBDIR).relativeFilePath("/" CUTEHMI_DIRS_EXTENSIONS_INSTALL_SUBDIR));
0157             CUTEHMI_DEBUG("Extensions directory: " << extensionsDirPath);
0158 
0159             QCoreApplication::addLibraryPath(extensionsDirPath);
0160             CUTEHMI_DEBUG("Library paths: " << QCoreApplication::libraryPaths());
0161 
0162             QQmlApplicationEngine engine;
0163             engine.addImportPath(extensionsDirPath);
0164             CUTEHMI_DEBUG("QML import paths: " << engine.importPathList());
0165 
0166             QStringList positionalArguments = data.cmd->positionalArguments();
0167 #ifndef CUTEHMI_DAEMON_FORCE_DEFAULT_OPTIONS
0168             QString extension;
0169             if (positionalArguments.length() > 0)
0170                 extension = positionalArguments.at(0);
0171             else
0172                 extension = DEFAULT_EXTENSION;
0173 
0174             QString extensionMinor = data.cmd->value(data.opt->minor);
0175             QString init = data.cmd->value(data.opt->init);
0176             QString component;
0177             if (positionalArguments.length() > 1)
0178                 component = positionalArguments.at(1);
0179             else
0180                 component = DEFAULT_COMPONENT;
0181 #else
0182             QString extension = DEFAULT_EXTENSION;
0183             QString component = DEFAULT_COMPONENT;
0184             QString extensionMinor = data.opt->minor.defaultValues().first();
0185             QString init = data.opt->init.defaultValues().first();
0186 
0187             if (positionalArguments.length() > 1)
0188                 throw Exception(QCoreApplication::translate("main", "You can not use 'component' option, because 'forceDefaultOptions' option has been set during compilation time.").toLocal8Bit().constData());
0189             if (positionalArguments.length() > 0)
0190                 throw Exception(QCoreApplication::translate("main", "You can not use 'extension' option, because 'forceDefaultOptions' option has been set during compilation time.").toLocal8Bit().constData());
0191             if (data.cmd->value(data.opt->minor) != extensionMinor)
0192                 throw Exception(QCoreApplication::translate("main", "You can not use '%1' option, because 'forceDefaultOptions' option has been set during compilation time.").arg(data.opt->minor.names().join(", ")).toLocal8Bit().constData());
0193             if (data.cmd->value(data.opt->init) != init)
0194                 throw Exception(QCoreApplication::translate("main", "You can not use '%1' option, because 'forceDefaultOptions' option has been set during compilation time.").arg(data.opt->init.names().join(", ")).toLocal8Bit().constData());
0195 #endif
0196             if (!extensionMinor.isEmpty()) {
0197                 bool ok;
0198                 extensionMinor.toUInt(& ok);
0199                 if (!ok)
0200                     throw Exception(QCoreApplication::translate("main", "Command line argument error: value of '%1' option must be a number.").arg(data.opt->minor.names().last()));
0201             }
0202             QString extensionBaseName = extension.left(extension.lastIndexOf('.'));
0203             QString extensionMajor = extension.right(extension.length() - extension.lastIndexOf('.') - 1);
0204             if (!extension.isEmpty()) {
0205                 bool ok;
0206                 extensionMajor.toUInt(& ok);
0207                 if (!ok)
0208                     throw Exception(QCoreApplication::translate("main", "Command line argument error: please specify extension with major version number after the last dot."));
0209             }
0210             engine.rootContext()->setContextProperty("cutehmi_daemon_extensionBaseName", extensionBaseName);
0211             engine.rootContext()->setContextProperty("cutehmi_daemon_extensionMajor", extensionMajor);
0212             engine.rootContext()->setContextProperty("cutehmi_daemon_extensionMinor", extensionMinor);
0213             engine.rootContext()->setContextProperty("cutehmi_daemon_extensionComponent", component);
0214 
0215             //<cutehmi.daemon-1.workaround target="Qt" cause="QTBUG-73649">
0216             // Class QQmlApplicationEngine connects Qt.quit() signal to QCoreApplication::quit() and QQmlApplicationEngine::exit()
0217             // signal to QCoreApplication::exit(), but it does so with AutoConnection. This causes in some circumstances problems,
0218             // which are described in Qt documentation (see QCoreApplication::exit()). Workaround is to disconnect signals and
0219             // connect them again with QueuedConnection.
0220             engine.disconnect(& engine, nullptr, data.app, nullptr);
0221             engine.connect(& engine, SIGNAL(quit()), data.app, SLOT(quit()), Qt::QueuedConnection);
0222             engine.connect(& engine, & QQmlApplicationEngine::exit, data.app, & QCoreApplication::exit, Qt::QueuedConnection);
0223             //</cutehmi.daemon-1.workaround>
0224 
0225             if (!init.isNull()) {
0226                 CUTEHMI_DEBUG("Init: '" << init << "'");
0227                 CUTEHMI_DEBUG("Extension: '" << extension << "'");
0228                 CUTEHMI_DEBUG("Minor: '" << extensionMinor << "'");
0229                 CUTEHMI_DEBUG("Component: '" << component << "'");
0230                 QUrl initUrl(init);
0231                 if (initUrl.isValid()) {
0232                     // Assure that URL is not mixing relative path with explicitly specified scheme, which is forbidden. QUrl::isValid() doesn't check this out.
0233                     if (!initUrl.scheme().isEmpty() && QDir::isRelativePath(initUrl.path()))
0234                         throw Exception(QCoreApplication::translate("main", "URL '%1' contains relative path along with URL scheme, which is forbidden.").arg(initUrl.url()));
0235                     else {
0236                         // If source URL is relative (does not contain scheme), then make absolute URL: file:///baseDirPath/sourceUrl.
0237                         if (initUrl.isRelative())
0238                             initUrl = QUrl::fromLocalFile(baseDirPath).resolved(initUrl);
0239                         // Check if file exists and eventually set context property.
0240                         if (initUrl.isLocalFile() && !QFile::exists(initUrl.toLocalFile()))
0241                             throw Exception(QCoreApplication::translate("main", "QML file '%1' does not exist.").arg(initUrl.url()));
0242                         else {
0243                             // Load extension translation and connect uiLanguageChanged() signal to retranslate() slot.
0244                             if (!extension.isEmpty())
0245                                 cutehmi::Internationalizer::Instance().loadTranslation(extension);
0246                             QObject::connect(& cutehmi::Internationalizer::Instance(), & cutehmi::Internationalizer::uiLanguageChanged, & engine, & QQmlApplicationEngine::retranslate);
0247 
0248                             engine.load(initUrl.url());
0249                             int result = data.app->exec();
0250 
0251                             engine.collectGarbage();
0252                             cutehmi::Internationalizer::Instance().unloadTranslations();
0253                             return result;
0254                         }
0255                     }
0256                 } else
0257                     throw Exception(QCoreApplication::translate("main", "Invalid format of QML file URL '%1'.").arg(init));
0258             } else
0259                 throw Exception(QCoreApplication::translate("main", "No initial loader has been specified."));
0260 
0261             return EXIT_SUCCESS;
0262         } catch (const Exception & e) {
0263             CUTEHMI_CRITICAL(e.what());
0264         } catch (const cutehmi::Messenger::NoAdvertiserException & e) {
0265             CUTEHMI_CRITICAL("Dialog message: " << e.message()->text());
0266             if (!e.message()->informativeText().isEmpty())
0267                 CUTEHMI_CRITICAL("Informative text: " << e.message()->informativeText());
0268             if (!e.message()->detailedText().isEmpty())
0269                 CUTEHMI_CRITICAL("Detailed text: " << e.message()->detailedText());
0270             CUTEHMI_CRITICAL("Available buttons: " << e.message()->buttons());
0271         } catch (const std::exception & e) {
0272             CUTEHMI_CRITICAL(e.what());
0273         } catch (...) {
0274             CUTEHMI_CRITICAL("Caught unrecognized exception.");
0275             throw;
0276         }
0277 
0278         return  EXIT_FAILURE;
0279     };
0280 
0281 
0282     // Run program core in daemon or application mode.
0283 
0284     int exitCode;
0285 
0286     if (!cmd.isSet(opt.app)) {
0287         Daemon daemon(& coreData, core);
0288 
0289         // At this point logging should be configured and printing facilities silenced. Not much to say anyways...
0290         //</cutehmi.daemon-silent_initialization.principle>
0291 
0292         exitCode = daemon.exitCode();
0293     } else
0294         exitCode = core(coreData);
0295 
0296     // Destroy singleton instances before QCoreApplication. Ignoring the recommendation to connect clean-up code to the
0297     // aboutToQuit() signal, because for daemon it is always violent termination if QCoreApplication::exec() does not exit.
0298     cutehmi::destroySingletonInstances();
0299 
0300     return exitCode;
0301 
0302     //</Qt-Qt_5_7_0_Reference_Documentation-Threads_and_QObjects-QObject_Reentrancy-creating_QObjects_before_QApplication.assumption>
0303 }
0304 
0305 //(c)C: Copyright © 2020-2024, Michał Policht <michal@policht.pl>. All rights reserved.
0306 //(c)C: SPDX-License-Identifier: LGPL-3.0-or-later OR MIT
0307 //(c)C: This file is a part of CuteHMI.
0308 //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
0309 //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
0310 //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI.  If not, see <https://www.gnu.org/licenses/>.
0311 //(c)C: Additionally, this file is licensed under terms of MIT license as expressed below.
0312 //(c)C: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
0313 //(c)C: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
0314 //(c)C: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.