5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include <QApplication>
23 #include <QCommandLineParser>
24 #include <QMessageBox>
26 #include <QTextStream>
27 #if defined(JOYSTICKS) || defined(SIMU_AUDIO)
33 #include "appdebugmessagehandler.h"
34 #include "constants.h"
35 #include "customdebug.h"
36 #include "eeprominterface.h"
37 #include "simulator.h"
38 #include "simulatormainwindow.h"
39 #include "simulatorstartupdialog.h"
41 #include "translations.h"
44 using namespace Simulator
;
47 int finish(int exitCode
);
49 void showMessage(const QString
& message
, enum QMessageBox::Icon icon
= QMessageBox::NoIcon
, bool useConsole
= false)
52 if (icon
< QMessageBox::Warning
)
53 QTextStream(stdout
) << message
<< endl
;
55 QTextStream(stderr
) << message
<< endl
;
61 msgBox
.setText("<html><body><pre>" + message
.toHtmlEscaped() + "</pre></body></html>");
63 msgBox
.setWindowTitle(QApplication::translate("SimulatorMain", "OpenTx Simulator"));
67 const QString
sharedHelpText()
70 QTextStream
stream(&ret
);
71 // list all available profiles
72 stream
<< endl
<< QApplication::translate("SimulatorMain", "Available profiles:") << endl
;
73 QMapIterator
<int, QString
> pi(g
.getActiveProfiles());
74 while (pi
.hasNext()) {
76 stream
<< "\t" << QApplication::translate("SimulatorMain", "ID: ") << pi
.key() << "; " << QApplication::translate("SimulatorMain", "Name: ") << pi
.value() << endl
;
78 // list all available radios
79 stream
<< endl
<< QApplication::translate("SimulatorMain", "Available radios:") << endl
;
80 foreach(QString name
, SimulatorLoader::getAvailableSimulators()) {
81 stream
<< "\t" << name
<< endl
;
86 void showHelp(QCommandLineParser
& parser
, const QString
& addMsg
= QString(), int exitCode
= 0) {
88 if (!addMsg
.isEmpty())
89 msg
.append(addMsg
).append("\n\n");
90 msg
.append(parser
.helpText());
91 msg
.append(sharedHelpText());
93 showMessage(msg
, (exitCode
? QMessageBox::Warning
: QMessageBox::Information
));
96 enum CommandLineParseResult
104 CommandLineParseResult
cliOptions(SimulatorOptions
* simOptions
, int * profileId
)
106 QCommandLineParser cliOptions
;
107 bool cliOptsFound
= false;
108 int pId
= *profileId
;
110 const QCommandLineOption optHelp
= cliOptions
.addHelpOption();
111 const QCommandLineOption optVer
= cliOptions
.addVersionOption();
113 const QCommandLineOption
optProfi(QStringList() << "profile" << "p",
114 QApplication::translate("SimulatorMain", "Radio profile ID or Name to use for simulator."),
115 QApplication::translate("SimulatorMain", "profile"));
117 const QCommandLineOption
optRadio(QStringList() << "radio" << "r",
118 QApplication::translate("SimulatorMain", "Radio type to simulate (usually defined in profile)."),
119 QApplication::translate("SimulatorMain", "radio"));
121 const QCommandLineOption
optSdDir(QStringList() << "sd-path" << "s",
122 QApplication::translate("SimulatorMain", "Directory containing the SD card image to use. The default is configured in the chosen Radio Profile."),
123 QApplication::translate("SimulatorMain", "path"));
125 const QCommandLineOption
optStart(QStringList() << "start-with" << "w",
126 QApplication::translate("SimulatorMain", "Data source type to use (applicable to Horus only). One of:") + " (file|folder|sd)",
127 QApplication::translate("SimulatorMain", "type"));
129 cliOptions
.addPositionalArgument(QApplication::translate("SimulatorMain", "data-source"),
130 QApplication::translate("SimulatorMain", "Radio data (.bin/.eeprom/.otx) image file to use OR data folder path (for Horus-style radios).\n"
131 "NOTE: any existing EEPROM data incompatible with the selected radio type may be overwritten!"),
132 QApplication::translate("SimulatorMain", "[data-source]"));
134 cliOptions
.addOption(optProfi
);
135 cliOptions
.addOption(optRadio
);
136 cliOptions
.addOption(optSdDir
);
137 cliOptions
.addOption(optStart
);
139 QStringList args
= QCoreApplication::arguments();
141 // For backwards compat. with QxtCommandOptions, convert Windows-style CLI switches (/opt) since QCommandLineParser doesn't support them
142 for (int i
=0; i
< args
.size(); ++i
) {
143 args
[i
].replace(QRegExp("^/([^\\s]{2,10})$"), "--\\1"); // long opts
144 args
[i
].replace(QRegExp("^/([^\\s]){1}$"), "-\\1"); // short opts
148 if (!cliOptions
.parse(args
)) {
149 showHelp(cliOptions
, cliOptions
.errorText());
150 return CommandLineExitErr
;
153 if (cliOptions
.isSet(optHelp
)) {
154 showHelp(cliOptions
);
155 return CommandLineExitOk
;
158 if (cliOptions
.isSet(optVer
)) {
159 showMessage(APP_SIMULATOR
% " v" VERSION
" " __DATE__
, QMessageBox::Information
);
160 return CommandLineExitOk
;
163 if (cliOptions
.isSet(optProfi
)) {
165 pId
= cliOptions
.value(optProfi
).toInt(&chk
);
167 pId
= g
.getActiveProfiles().key(cliOptions
.value(optProfi
), -1);
169 if (!g
.getActiveProfiles().contains(pId
)) {
170 showHelp(cliOptions
, QApplication::translate("SimulatorMain", "Error: Profile ID %1 was not found.").arg(pId
));
171 return CommandLineExitErr
;
174 *simOptions
= g
.profile
[pId
].simulatorOptions();
178 if (cliOptions
.isSet(optRadio
)) {
179 simOptions
->firmwareId
= cliOptions
.value(optRadio
);
183 if (cliOptsFound
|| (simOptions
->dataFile
.isEmpty() && !simOptions
->firmwareId
.isEmpty())) {
184 // this constructs a new default radio data file name in the user-configured eeprom directory
185 simOptions
->dataFile
= SimulatorStartupDialog::radioEepromFileName(simOptions
->firmwareId
, g
.eepromDir());
188 if (cliOptions
.isSet(optSdDir
)) {
189 simOptions
->sdPath
= cliOptions
.value(optSdDir
);
193 if (cliOptions
.positionalArguments().size()) {
194 QString datasrc
= cliOptions
.positionalArguments().at(0);
195 if (datasrc
.contains(QRegExp(".*\\.[\\w]{2,6}$"))) {
196 simOptions
->dataFile
= datasrc
;
197 simOptions
->startupDataType
= SimulatorOptions::START_WITH_FILE
;
200 simOptions
->dataFolder
= datasrc
;
201 simOptions
->startupDataType
= SimulatorOptions::START_WITH_FOLDER
;
206 if (cliOptions
.isSet(optStart
)) {
207 QString stTyp
= cliOptions
.value(optStart
);
208 if (stTyp
== "file") {
209 simOptions
->startupDataType
= SimulatorOptions::START_WITH_FILE
;
211 else if (stTyp
== "folder") {
212 simOptions
->startupDataType
= SimulatorOptions::START_WITH_FOLDER
;
214 else if (stTyp
== "sd") {
215 simOptions
->startupDataType
= SimulatorOptions::START_WITH_SDPATH
;
218 showHelp(cliOptions
, QApplication::translate("SimulatorMain", "Unrecognized startup data source type: %1").arg(stTyp
));
219 return CommandLineExitErr
;
227 return CommandLineFound
;
229 return CommandLineNone
;
232 int main(int argc
, char *argv
[])
235 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
236 /* From doc: This attribute must be set before Q(Gui)Application is constructed. */
237 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling
);
240 QApplication
app(argc
, argv
);
241 app
.setApplicationName(APP_SIMULATOR
);
242 app
.setApplicationVersion(VERSION
);
243 app
.setOrganizationName(COMPANY
);
244 app
.setOrganizationDomain(COMPANY_DOMAIN
);
246 Q_INIT_RESOURCE(companion
);
248 if (AppDebugMessageHandler::instance())
249 AppDebugMessageHandler::instance()->installAppMessageHandler();
251 CustomDebug::setFilterRules();
253 g
.init(); // init settings before installing translations
255 if (AppDebugMessageHandler::instance() && g
.appDebugLog() && !g
.appLogsDir().isEmpty() && QDir().mkpath(g
.appLogsDir())) {
256 QString fn
= g
.appLogsDir() % "/SimulatorDebug_" % QDateTime::currentDateTime().toString("yy-MM-dd_HH-mm-ss") % ".log";
257 simuDbgLog
.setFileName(fn
);
258 if (simuDbgLog
.open(QIODevice::WriteOnly
| QIODevice::Text
)) {
259 AppDebugMessageHandler::instance()->addOutputDevice(&simuDbgLog
);
263 Translations::installTranslators();
265 #if defined(JOYSTICKS) || defined(SIMU_AUDIO)
266 uint32_t sdlFlags
= 0;
268 sdlFlags
|= SDL_INIT_JOYSTICK
;
271 sdlFlags
|= SDL_INIT_AUDIO
;
273 if (SDL_Init(sdlFlags
) < 0) {
274 showMessage(QApplication::translate("SimulatorMain", "WARNING: couldn't initialize SDL:\n%1").arg(SDL_GetError()), QMessageBox::Warning
);
278 registerStorageFactories();
279 registerOpenTxFirmwares();
280 SimulatorLoader::registerSimulators();
282 if (!SimulatorLoader::getAvailableSimulators().size()) {
283 showMessage(QApplication::translate("SimulatorMain", "ERROR: No simulator libraries available."), QMessageBox::Critical
);
287 int profileId
= (g
.simuLastProfId() > -1 ? g
.simuLastProfId() : g
.id());
288 SimulatorOptions simOptions
= g
.profile
[profileId
].simulatorOptions();
290 // TODO : defaults should be set in Profile::init()
291 if (simOptions
.firmwareId
.isEmpty())
292 simOptions
.firmwareId
= SimulatorLoader::findSimulatorByFirmwareName(g
.profile
[profileId
].fwType());
293 if (simOptions
.dataFolder
.isEmpty())
294 simOptions
.dataFolder
= g
.eepromDir();
295 if (simOptions
.sdPath
.isEmpty())
296 simOptions
.sdPath
= g
.profile
[profileId
].sdPath();
298 // Handle startup options
300 // check for command-line options
301 CommandLineParseResult cliResult
= cliOptions(&simOptions
, &profileId
);
303 if (cliResult
== CommandLineExitOk
)
305 if (cliResult
== CommandLineExitErr
)
308 // Present GUI startup options dialog if necessary
309 if (cliResult
== CommandLineNone
|| profileId
== -1 || simOptions
.firmwareId
.isEmpty() || (simOptions
.dataFile
.isEmpty() && simOptions
.dataFolder
.isEmpty())) {
310 SimulatorStartupDialog
* dlg
= new SimulatorStartupDialog(&simOptions
, &profileId
);
311 int ret
= dlg
->exec();
313 if (ret
!= QDialog::Accepted
) {
317 qDebug() << "Starting with options: profileId=" << profileId
<< simOptions
;
319 // Validate startup options
322 if (profileId
< 0 || simOptions
.firmwareId
.isEmpty() || (simOptions
.dataFile
.isEmpty() && simOptions
.dataFolder
.isEmpty())) {
323 resultMsg
= QApplication::translate("SimulatorMain", "ERROR: Couldn't start simulator, missing radio/profile/data file/folder.\n Profile ID: [%1]; Radio ID: [%2];\nData File: [%3]");
324 showMessage(resultMsg
.arg(profileId
).arg(simOptions
.firmwareId
, simOptions
.dataFile
), QMessageBox::Critical
);
327 if (!g
.getActiveProfiles().contains(profileId
) || !SimulatorLoader::getAvailableSimulators().contains(simOptions
.firmwareId
)) {
328 QTextStream
stream(&resultMsg
);
329 stream
<< QApplication::translate("SimulatorMain", "ERROR: Radio profile or simulator firmware not found.\nProfile ID: [%1]; Radio ID: [%2]").arg(profileId
).arg(simOptions
.firmwareId
);
330 stream
<< sharedHelpText();
331 showMessage(resultMsg
, QMessageBox::Critical
);
335 // All checks passed, save profile ID and start simulator
337 g
.sessionId(profileId
);
338 g
.simuLastProfId(profileId
);
340 // Set global firmware environment
341 Firmware::setCurrentVariant(Firmware::getFirmwareForId(simOptions
.firmwareId
));
344 SimulatorMainWindow
* mainWindow
= new SimulatorMainWindow(NULL
, simOptions
.firmwareId
, SIMULATOR_FLAGS_STANDALONE
);
345 if ((result
= mainWindow
->getExitStatus(&resultMsg
))) {
346 if (resultMsg
.isEmpty())
347 resultMsg
= QApplication::translate("SimulatorMain", "Uknown error during Simulator startup.");
348 showMessage(resultMsg
, QMessageBox::Critical
);
350 else if (mainWindow
->setOptions(simOptions
, true)) {
354 if ((result
= mainWindow
->getExitStatus(&resultMsg
)) && !resultMsg
.isEmpty())
355 qWarning() << "Exit message from SimulatorMainWindow:" << resultMsg
;
363 return finish(result
);
366 int finish(int exitCode
)
368 SimulatorLoader::unregisterSimulators();
369 unregisterOpenTxFirmwares();
370 unregisterStorageFactories();
372 #if defined(JOYSTICKS) || defined(SIMU_AUDIO)
376 qDebug() << "SIMULATOR EXIT" << exitCode
;
377 if (simuDbgLog
.isOpen()) {
378 AppDebugMessageHandler::instance()->removeOutputDevice(&simuDbgLog
);