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.