524 lines
19 KiB
C++
Executable File
524 lines
19 KiB
C++
Executable File
/**
|
|
* @RDE_HEADER@
|
|
* main for OperatorApplication
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
*/
|
|
|
|
#include <LtsSession/lts_session.h>
|
|
#include <LtsSession/lts_session_manager.h>
|
|
#include <LtsAppManager/lts_app_manager.h>
|
|
#include <LtsPluginManager/lts_plugin_loader.h>
|
|
#include <LtsSession/lts_session_data.h>
|
|
#include <LtsCommandLineParser/LtsCommandLineParser.h>
|
|
#include <LtsMultiMonitor/lts_multi_monitor.h>
|
|
#include <StyleManager/style_manager.h>
|
|
#include <LtsWidgets/lts_global_wait_dialog.h>
|
|
#include <LtsWidgets/lts_splash.h>
|
|
#include <LtsWidgets/lts_combo_box.h>
|
|
#include <LtsProjectionWorkstation/lts_projection_workstation.h>
|
|
#include <LtsUtilsSettings/settings.h>
|
|
#include <nglapplication/nglapplication.h>
|
|
#include <nglapplication/nglloggerhandler.h>
|
|
#include <nglsettings/nglsettings_settings.h>
|
|
#include <ngltranslator/ngltranslator.h>
|
|
#include <NGLLogger/INGLLogger.h>
|
|
#include <rti/config/Logger.hpp>
|
|
|
|
#include <QPixmap>
|
|
#include <QProcess>
|
|
#include <QDir>
|
|
#include <QString>
|
|
#include <QMessageBox>
|
|
#include <QSurfaceFormat>
|
|
#include <QScrollerProperties>
|
|
#include <QInputDialog>
|
|
#include <QNetworkProxy>
|
|
#include <QTimer>
|
|
|
|
|
|
class CmdLineParser : public LtsCommandLineParser::LtsCommandLineParser
|
|
{
|
|
public:
|
|
CmdLineParser()
|
|
: LtsCommandLineParser(ProvideCommandLineParameterOptions(CreateDomainIdFromIp | DomainId | ExerciseId | TcpConnectionUrl))
|
|
{
|
|
addOption({{QStringLiteral("a"), QStringLiteral("application")},
|
|
QStringLiteral("Specify the initial application to start (Default: Situation [Demo / Dev - otherwise NOT SPECIFIED => starts AppChooser]"), QStringLiteral("Application"),
|
|
QStringLiteral("Situation")
|
|
}); // TODO: remove default on release
|
|
|
|
addOption({{QStringLiteral("b"), QStringLiteral("basePlugin")},
|
|
QStringLiteral("Base Project Plugin Iid to start the application with (Default: <not specified>)"), QStringLiteral("Base Project Plugin Iid"),
|
|
QString()
|
|
}); // TODO: remove default on release
|
|
|
|
addOption({{QStringLiteral("s"), QStringLiteral("stylePlugin")},
|
|
QStringLiteral("Style Plugin to start the application with (Default: rme.light)"), QStringLiteral("Style Plugin Iid"),
|
|
QStringLiteral("rme.light")
|
|
});
|
|
|
|
addOption({{QStringLiteral("u"), QStringLiteral("username")},
|
|
QStringLiteral("Preset Username to skip login (also specify -p !)"),
|
|
QStringLiteral("username")
|
|
});
|
|
|
|
addOption({{QStringLiteral("x"), QStringLiteral("tabletmode")},
|
|
QStringLiteral("Enforce Tablet Mode"),
|
|
QStringLiteral("false")
|
|
});
|
|
|
|
addOption({{QStringLiteral("w"), QStringLiteral("workstationmode")},
|
|
QStringLiteral("Enforce Workstation Mode"),
|
|
QStringLiteral("false")
|
|
});
|
|
|
|
addOption({{QStringLiteral("q"), QStringLiteral("qos_file")},
|
|
QStringLiteral("Define QoS Profile "),
|
|
QStringLiteral("qos_file")
|
|
});
|
|
|
|
|
|
addOption({{QStringLiteral("p"), QStringLiteral("password")},
|
|
QStringLiteral("Preset Password to skip login (also specify -u !)"),
|
|
QStringLiteral("password")
|
|
});
|
|
|
|
addOption({{QStringLiteral("k"), QStringLiteral("skipScreen")},
|
|
QStringLiteral("Tell the Multi Monitor mode to not put anything on the screen with id n (example: 1 (skips entire screen 1)) - or on sub screen 0 or 1 (left or right) for ultra wide screens (example: 1.1 (skips the right side of screen 1)) (Default: (none))"),
|
|
QStringLiteral("screen id"),
|
|
});
|
|
|
|
addOption({{QStringLiteral("m"), QStringLiteral("multi-monitor-mode")},
|
|
QStringLiteral("Use the Multi Monitor mode to place Base Layouts on Screens in Fullscreen")
|
|
});
|
|
|
|
#ifdef LTS_DEBUG
|
|
addOption({QStringLiteral("dbgmmm"),
|
|
QStringLiteral("Start the multi-monitor-mode with reduced size for use on development maschines")
|
|
});
|
|
#endif
|
|
|
|
addOption({{QStringLiteral("r"), QStringLiteral("replayId")},
|
|
QStringLiteral("Default: -1"),
|
|
QStringLiteral("Replay ID"),
|
|
QStringLiteral("-1")
|
|
});
|
|
|
|
addOption({{QStringLiteral("l"), QStringLiteral("lang")},
|
|
QStringLiteral("Language iso code to use (de_DE, en_GB, en_US)"),
|
|
QStringLiteral("<locale iso code>")
|
|
});
|
|
|
|
addOption({QStringLiteral("ignore-plugins"),
|
|
QStringLiteral("Ignore these plugins, which otherwise would be loaded, "
|
|
"specified by apps.json of the selected Base Plugin. "
|
|
"Example: \"--ignore-plugins data:avm,data:bdv,gui:avm\""),
|
|
QStringLiteral("ignore-plugins") // <= value
|
|
});
|
|
|
|
addOption({QStringLiteral("projection"),
|
|
QStringLiteral("Application is started on an Projection Workstation."),
|
|
});
|
|
|
|
addOption({QStringLiteral("check-requirements"),
|
|
QStringLiteral("Lets the Plugin Loader check if every App's requirements are met before starting"),
|
|
});
|
|
addOption({QStringLiteral("enable-experimental-web-platform-features"),
|
|
QString(),
|
|
});
|
|
|
|
addOption({QStringLiteral("script"),
|
|
QStringLiteral("Id of a script. The script has to exist in the given exercise."),
|
|
QStringLiteral("ScriptId"),
|
|
QStringLiteral("0")
|
|
});
|
|
|
|
addOption({QStringLiteral("place-script-screens"),
|
|
QStringLiteral("Let the screen 1 and screen 2 of the script-editor be placed automatically on startup.")
|
|
});
|
|
|
|
m_additionalHelpStr =
|
|
QStringLiteral("\n\n\e[33m example OWS : %1 -m -a0 -d1 -b legatus -t EXCTDS01:7400 -l de_DE \n\n\e[0m").arg(QCoreApplication::arguments().at(0));
|
|
|
|
// fprintf(stdout, "\n\n\e[33m example OWS : %s -m -a0 -d1 -b legatus -t EXCTDS01:7400 -l de_DE \n\n\e[0m", QCoreApplication::arguments().at(0).toLatin1().data());
|
|
|
|
}
|
|
|
|
QString operatorName() const { return value(QStringLiteral("u")); }
|
|
QString password() const { return value(QStringLiteral("p")); }
|
|
int replayId() const { return value(QStringLiteral("r")).toInt(); }
|
|
QString projectPluginIid() const { return value(QStringLiteral("b")); }
|
|
QString stylePluginIid() const { return value(QStringLiteral("s")); }
|
|
int applicationId() const { bool idk = false; int id = value(QStringLiteral("a")).toInt(&idk); if (!idk) { return -1; } return id; }
|
|
bool multiMonitorMode() const { return isSet(QStringLiteral("m")); }
|
|
bool isProjectionWorkstation() const { return isSet(QStringLiteral("projection")); }
|
|
bool isForcedTabletMode() const { return isSet(QStringLiteral("x")); }
|
|
bool isForcedWSMode() const { return isSet(QStringLiteral("w")); }
|
|
QString langIsoCode() const { return value(QStringLiteral("l")); }
|
|
QStringList ignoredPlugins() const { return value(QStringLiteral("ignore-plugins")).split(QLatin1Char(','), QString::SkipEmptyParts); }
|
|
bool checkRequirements() const { return isSet(QStringLiteral("check-requirements")); }
|
|
QString skipScreen() const { return value(QStringLiteral("k")); }
|
|
QString qosFile() const { return value(QStringLiteral("q")).size() > 0 ? value(QStringLiteral("q")) : QString(); }
|
|
int scriptId() const { return value(QStringLiteral("script")).toInt(); }
|
|
bool placeScriptScreens() const { return isSet(QStringLiteral("place-script-screens")); }
|
|
#ifdef LTS_DEBUG
|
|
bool debugMultiMonitorMode() const { return isSet(QStringLiteral("dbgmmm")); }
|
|
#endif
|
|
};
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
int main(int argc, char **argv) // TODO: json strings to lower
|
|
{
|
|
qsrand(time(nullptr));
|
|
|
|
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
|
|
|
|
QScrollerProperties props;
|
|
props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
|
|
props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
|
|
|
|
QScrollerProperties::setDefaultScrollerProperties(props);
|
|
|
|
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
|
//LtsApp app(argc, argv);
|
|
NGLApplication app(argc, argv);
|
|
app.createLogger("opApp");
|
|
QApplication::setQuitOnLastWindowClosed(false); // we are cool enough to handle it ourselves 8)
|
|
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
|
QNetworkProxy::setApplicationProxy(QNetworkProxy()); // do NOT use proxies anywhere!
|
|
|
|
NGL::LoggerHandler *lh = NGL::LoggerHandler::getLoggerHandler();
|
|
if (lh)
|
|
{
|
|
const QByteArray postfix = lh->loggerNamePostfix().toLocal8Bit() + "_dds_";
|
|
const QByteArray prefix = lh->loggerNamePrefix().toLocal8Bit();
|
|
|
|
rti::config::Logger &ddsLogger = rti::config::Logger::instance();
|
|
//ddsLogger.output_file(fdopen(STDOUT_FILENO, "w"));
|
|
ddsLogger.output_file_set(postfix.data(), prefix.data(), 10 * 1024 * 1024, 10);
|
|
ddsLogger.verbosity(rti::config::Verbosity_def::WARNING);
|
|
//ddsLogger.verbosity_by_category(rti::config::LogCategory_def::COMMUNICATION, rti::config::Verbosity_def::STATUS_ALL);
|
|
}
|
|
|
|
|
|
// TODO: replace all exit(x) with qApp.quit() and an error msg
|
|
|
|
LtsWidgets::LtsGlobalWaitDialog::getInstance()->setVisible(false); // avoid wait screen, we have the splash for this reason!
|
|
|
|
#ifdef __linux__
|
|
// svcSituationRep - pgrep/pkill length limitation
|
|
QProcess::startDetached(QLatin1String("pkill -9 svcSituationRep")); // kill it!
|
|
QProcess::startDetached(QLatin1String("pkill -9 ffmpeg")); // otherwise a stray ffmpeg may record until the the disk is full
|
|
#elif _WIN32
|
|
QProcess::startDetached(QLatin1String("taskkill /im svcSituationReplay /f"));
|
|
#endif
|
|
|
|
// Parse Options
|
|
CmdLineParser options;
|
|
if (!options.init())
|
|
return -1;
|
|
|
|
// Set Translation
|
|
|
|
QString langIsoCode = options.langIsoCode();
|
|
if (langIsoCode.isEmpty())
|
|
langIsoCode = QLocale::system().name();
|
|
NGL::setTranslationLocale(langIsoCode.toLatin1());
|
|
|
|
|
|
// Set Style
|
|
QString stylePluginName = options.stylePluginIid();
|
|
if (stylePluginName.isEmpty())
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "No Style Plugin Iid specified - abort.";
|
|
return -1;
|
|
}
|
|
|
|
// .. to keep some compatibility to old resource structure TODO: rm !
|
|
QDir::setSearchPaths(QLatin1String("icon"), QStringList(QLatin1String(":/style/icons/")));
|
|
QDir::setSearchPaths(QLatin1String("icon"), QStringList(QLatin1String(":/style/"))); // FIXME: does qt check for recursion ??
|
|
|
|
|
|
LtsUtils::StyleManager::getInstance()->loadStyle(stylePluginName);
|
|
|
|
|
|
// Load Plugins
|
|
LtsPluginManager::LtsPluginLoader *pluginLoader = LtsPluginManager::LtsPluginLoader::getInstance();
|
|
|
|
QString projectPluginName = options.projectPluginIid();
|
|
|
|
if (projectPluginName.isEmpty())
|
|
{
|
|
bool k;
|
|
projectPluginName = QInputDialog::getText(nullptr,
|
|
QCoreApplication::tr("No Project Plugin specified"),
|
|
QCoreApplication::tr("Please specify a Project Plugin:"), QLineEdit::Normal,
|
|
QString(), &k); // blocking, no event loop needed
|
|
|
|
if (!k || projectPluginName.isEmpty())
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "No Project Plugin Iid specified - abort.";
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// load project plugin
|
|
if (!pluginLoader->loadPlugins(LtsPluginManager::PluginType::PROJECT, nullptr, {projectPluginName}))
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "Error loading Project Plugin - abort.";
|
|
return -1;
|
|
}
|
|
|
|
LtsUtils::ProjectSettings::initFromFile(QStringLiteral(":project/project_settings.json"));
|
|
|
|
// Setup Application
|
|
if (options.multiMonitorMode())
|
|
LtsMultiMonitor::LtsMultiMonitor::getInstance()->setEnableMultiMonitorMode();
|
|
#ifdef LTS_DEBUG
|
|
if (options.debugMultiMonitorMode())
|
|
LtsMultiMonitor::LtsMultiMonitor::getInstance()->setDebugMultiMonitorMode();
|
|
#endif
|
|
|
|
|
|
// show splash
|
|
LtsWidgets::Splash::getInstance()->show();
|
|
|
|
LtsAppDescriptor::App applicationId = LtsAppDescriptor::LtsAppDescriptor::appFromInteger(options.applicationId());
|
|
int exerciseId = options.exerciseId(); // -1 = invalid, 0 = admin, >0 = exercise
|
|
int replayId = options.replayId(); // -1 = invalid, TODO: comment doku
|
|
int scriptId = options.scriptId(); // <0 = invalid, 0 = default = ignore, >0 script
|
|
QString operatorName = options.operatorName();
|
|
QString password = options.password();
|
|
QStringList qos_file = QStringList(options.qosFile());
|
|
|
|
// TODO: log4cxx/NGLLlogger does not flush here !! messages not seen !
|
|
|
|
if (scriptId < 0)
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "Given scriptId " << scriptId << " is invalid - abort.";
|
|
return -1;
|
|
}
|
|
|
|
if (scriptId > 0 && applicationId != LtsAppDescriptor::App::Scripts)
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "ScriptId set but given app is not Scripts - abort.";
|
|
return -1;
|
|
}
|
|
|
|
if (scriptId > 0 && exerciseId < 1)
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "ScriptId set but no exercise is given - abort.";
|
|
return -1;
|
|
}
|
|
|
|
if (scriptId > 0 && exerciseId > 0 && applicationId == LtsAppDescriptor::App::Scripts)
|
|
{
|
|
LtsSessionData::LtsSessionData::getInstance()->setScriptId(scriptId);
|
|
}
|
|
|
|
if (options.placeScriptScreens() && applicationId != LtsAppDescriptor::App::Scripts)
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "PlaceScriptScreensn is set but given app is not Scripts - abort.";
|
|
return -1;
|
|
}
|
|
|
|
LtsSessionData::LtsSessionData::getInstance()->setPlaceScriptScreens(options.placeScriptScreens());
|
|
|
|
if (operatorName.isEmpty() != password.isEmpty()) // check for xor and warn user
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "Specify -u if -p is specified & vise versa - abort.";
|
|
return 1;
|
|
}
|
|
|
|
if (options.domainId() < 0)
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "Invalid configuration for domainId (" << options.domainId() << ") from LtsConfig - abort.";
|
|
QMessageBox::critical(nullptr, QCoreApplication::tr("Invalid Domain Id"), QCoreApplication::tr("Domain #%1 is not valid - abort.").arg(options.domainId()));
|
|
return -1;
|
|
}
|
|
// }
|
|
|
|
|
|
LtsSessionData::LtsSessionData *sessionData = LtsSessionData::LtsSessionData::getInstance();
|
|
|
|
sessionData->setDomainIdPreset(options.domainId());
|
|
sessionData->setRequestedAppId(applicationId);
|
|
|
|
if (options.useTcpConnection())
|
|
sessionData->setTcpConnectionUrl(options.tcpConnectionUrl());
|
|
|
|
if (options.checkRequirements())
|
|
sessionData->setDebugFlagValue(QStringLiteral("PluginManager.CheckAppRequirements"), true);
|
|
|
|
// load JSON app description file
|
|
QFile appsFile(QStringLiteral(":project/apps.json")); // TODO: convert Json => Binary Json
|
|
|
|
if (!appsFile.open(QFile::ReadOnly))
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "Could not read App Description File";
|
|
return -1;
|
|
}
|
|
|
|
QStringList lastErrors;
|
|
QString loginPluginIid;
|
|
|
|
LtsAppDescriptor::AppDescriptionVector appDescs = LtsAppDescriptor::LtsAppDescriptor::getAppDescriptionVector(appsFile.readAll(),
|
|
lastErrors,
|
|
options.ignoredPlugins(),
|
|
loginPluginIid);
|
|
|
|
if (loginPluginIid.isEmpty())
|
|
loginPluginIid = QStringLiteral("opm.login");
|
|
|
|
QString skipScreen = options.skipScreen();
|
|
|
|
int skipScreenId = -1;
|
|
int skipSubScreenId = -1;
|
|
|
|
if (!skipScreen.isEmpty())
|
|
{
|
|
QStringList skipScreenList(skipScreen.split(QLatin1Char('.'), QString::SkipEmptyParts));
|
|
|
|
int skipScreenListCount = skipScreenList.count();
|
|
|
|
if (skipScreenListCount > 2)
|
|
{
|
|
qWarning() << "invalid skip screen syntax:" << skipScreen;
|
|
return -1;
|
|
}
|
|
|
|
if (skipScreenListCount > 0)
|
|
{
|
|
bool k = false;
|
|
skipScreenId = skipScreenList.at(0).toInt(&k);
|
|
|
|
if (!k)
|
|
{
|
|
qWarning() << "invalid skip screen syntax:" << skipScreen;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (skipScreenListCount > 1)
|
|
{
|
|
bool k = false;
|
|
skipSubScreenId = skipScreenList.at(1).toInt(&k);
|
|
|
|
if (!k)
|
|
{
|
|
qWarning() << "invalid skip screen syntax:" << skipScreen;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
LtsMultiMonitor::LtsMultiMonitor::getInstance()->setSkipScreen(skipScreenId, skipSubScreenId);
|
|
|
|
appsFile.close();
|
|
|
|
if (!lastErrors.isEmpty())
|
|
{
|
|
QMessageBox::critical(nullptr, QString(), lastErrors.join(QStringLiteral("\r\n\r\n")));
|
|
// TODO: log precise msg !!
|
|
return -1;
|
|
}
|
|
|
|
|
|
if(options.isForcedTabletMode())
|
|
sessionData->setAsTablet();
|
|
else if (options.isForcedWSMode())
|
|
sessionData->setAsTablet(false);
|
|
else if (!QTouchDevice::devices().isEmpty())
|
|
sessionData->setAsTablet();
|
|
LtsWidgets::LtsComboBox::setUseSelectionDialog(sessionData->isTablet());
|
|
|
|
LtsAppManager::LtsAppManager *appManager = LtsAppManager::LtsAppManager::getInstance();
|
|
appManager->setAvailableApps(appDescs);
|
|
|
|
|
|
// Projection Specific Setup
|
|
if (options.isProjectionWorkstation())
|
|
{
|
|
if (operatorName.isEmpty() && password.isEmpty())
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "Projection-User & Password has to be specified when --projection option is active!";
|
|
return 1;
|
|
}
|
|
|
|
applicationId = LtsAppDescriptor::App::Situation;
|
|
|
|
LtsProjectionWorkstation::LtsProjectionWorkstation::getInstance()->setProjectionOperatorPreset(operatorName, password);
|
|
}
|
|
// Normal Setup
|
|
else
|
|
{
|
|
if (applicationId == LtsAppDescriptor::App::Login)
|
|
INFO_LOG(app.baseLogger()) << "No Application specified - starting with App Chooser.";
|
|
|
|
// TODO: replayid can not life without exerciseId
|
|
if (exerciseId >= 0 && replayId >= 0) // do not set both!
|
|
{
|
|
ERROR_LOG(app.baseLogger()) << "Use either -e (exercise) or -r (replay) - abort.";
|
|
return 1;
|
|
}
|
|
|
|
if (!operatorName.isEmpty() && !password.isEmpty())
|
|
{
|
|
sessionData->setOperatorPreset(operatorName, password); // if -u and -p (no Login window !)
|
|
}
|
|
|
|
if (exerciseId == 0) // Admin Session was requested explicitly
|
|
{
|
|
//sessionManager->setGlobalSessionVisible();
|
|
sessionData->setPartitionPreset(exerciseId, IDL::PartitionType::Global);
|
|
}
|
|
|
|
if (!qos_file.isEmpty()) // use defined qos file
|
|
{
|
|
//sessionManager->setGlobalSessionVisible();
|
|
DEBUG_LOG(app.baseLogger()) << "QOS -> using new file" << qos_file.join(QStringLiteral(","));
|
|
sessionData->setQosFile(qos_file);
|
|
}
|
|
|
|
if (exerciseId > 0) // non Admin => Exercise
|
|
sessionData->setPartitionPreset(exerciseId, IDL::PartitionType::Exercise);
|
|
|
|
if (replayId >= 0) // Replay
|
|
sessionData->setPartitionPreset(replayId, IDL::PartitionType::Replay);
|
|
}
|
|
|
|
appManager->setLoginPluginIid(loginPluginIid);
|
|
appManager->setMainApp(applicationId);
|
|
if (applicationId == LtsAppDescriptor::App::ThpSituationReplay)
|
|
{
|
|
LtsSessionData::LtsSessionData::getInstance()->setVisibleParties({});
|
|
LtsSessionData::LtsSessionData::getInstance()->setOperatorRights({-1});
|
|
QTimer::singleShot(0, appManager, [qos_file, appManager]()
|
|
{
|
|
LtsSession::DdsConnectionParams params;
|
|
params.domainId = 1;
|
|
params.exerciseId = 2;
|
|
params.partitionId = 2;
|
|
if (!qos_file.isEmpty())
|
|
params.qos_file = qos_file;
|
|
|
|
params.partitionType = IDL::PartitionType::Replay;
|
|
appManager->createApp(LtsAppDescriptor::App::Situation, params, {}, false, true);
|
|
} );
|
|
}
|
|
else
|
|
{
|
|
appManager->showLogin();
|
|
}
|
|
|
|
// Startup Application
|
|
STARTUP_LOG(app.baseLogger());
|
|
int iRet = app.exec();
|
|
SHUTDOWN_LOG(app.baseLogger());
|
|
|
|
return iRet;
|
|
}
|