2 ******************************************************************************
5 * @author The LibrePilot Team http://www.librepilot.org Copyright (C) 2017.
6 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
7 * Parts by Nokia Corporation (qt-info@nokia.com) Copyright (C) 2009.
9 * @see The GNU Public License (GPL) Version 3
13 *****************************************************************************/
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 3 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful, but
21 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
25 * You should have received a copy of the GNU General Public License along
26 * with this program; if not, write to the Free Software Foundation, Inc.,
27 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 The GCS application name is defined in the top level makefile - GCS_BIG_NAME / GCS_SMALL_NAME, and
32 set for the build in ../../gcs.pri and ./app.pro
34 The GCS locale is set to the system locale by default unless the "hidden" setting General/Locale has a value.
35 The user can not change General/Locale from the Options dialog.
37 The GCS language will default to the GCS locale unless the General/OverrideLanguage has a value.
38 The user can change General/OverrideLanguage to any available language from the Options dialog.
40 Both General/Locale and General/OverrideLanguage can be set from the command line or through the the factory defaults file.
42 The -D option is used to permanently set a user setting.
44 The -reset switch will clear all the user settings and will trigger a reload of the factory defaults.
46 You can combine it with the -config-file=<file> command line argument to quickly switch between multiple settings files.
49 gcs -reset -config-file ./MyGCS.xml
52 Relative paths are relative to <install dir>/share/$(GCS_SMALL_NAME)/configurations/
54 The specified file will be used to load the factory defaults from but only when the user settings are empty.
55 If the user settings are not empty the file will not be used.
56 This switch is useful on the 1st run when the user settings are empty or in combination with -reset.
59 Quickly switch configurations
62 gcs -reset -config-file <relative or absolute path>
65 Configuring GCS from installer
67 The -D option is used to permanently set a user setting.
69 If the user chooses to start GCS at the end of the installer:
72 gcs -D General/OverrideLanguage=de
75 If the user chooses not to start GCS at the end of the installer, you still need to configure GCS.
76 In that case you can use -exit-after-config
79 gcs -D General/OverrideLanguage=de -exit-after-config
84 #include "qtsingleapplication.h"
85 #include "gcssplashscreen.h"
86 #include "utils/pathutils.h"
87 #include "utils/settingsutils.h"
88 #include "utils/filelogger.h"
90 #include <extensionsystem/pluginmanager.h>
91 #include <extensionsystem/pluginspec.h>
92 #include <extensionsystem/iplugin.h>
94 #include <QtCore/QDir>
95 #include <QtCore/QElapsedTimer>
96 #include <QtCore/QTextStream>
97 #include <QtCore/QFileInfo>
98 #include <QtCore/QDebug>
99 #include <QtCore/QTimer>
100 #include <QtCore/QLibraryInfo>
101 #include <QtCore/QTranslator>
102 #include <QtCore/QSettings>
104 #include <QMessageBox>
105 #include <QApplication>
106 #include <QSurfaceFormat>
108 typedef QList
<ExtensionSystem::PluginSpec
*> PluginSpecSet
;
109 typedef QMap
<QString
, bool> AppOptions
;
110 typedef QMap
<QString
, QString
> AppOptionValues
;
112 const int OptionIndent
= 4;
113 const int DescriptionIndent
= 24;
115 const QLatin1String
APP_NAME(GCS_BIG_NAME
);
116 const QLatin1String
ORG_NAME(ORG_BIG_NAME
);
118 const QLatin1String
CORE_PLUGIN_NAME("Core");
120 const char *DEFAULT_CONFIG_FILENAME
= "default.xml";
122 const char *fixedOptionsC
= " [OPTION]... [FILE]...\n"
124 " -help Display this help\n"
125 " -version Display application version\n"
126 " -no-splash Don't display splash screen\n"
127 " -log <file> Log to specified file\n"
128 " -client Attempt to connect to already running instance\n"
129 " -D <key>=<value> Permanently set a user setting, e.g: -D General/OverrideLanguage=de\n"
130 " -reset Reset user settings to factory defaults.\n"
131 " -config-file <file> Specify alternate factory defaults settings file (used with -reset)\n"
132 " -exit-after-config Exit after manipulating configuration settings\n";
134 const QLatin1String
HELP1_OPTION("-h");
135 const QLatin1String
HELP2_OPTION("-help");
136 const QLatin1String
HELP3_OPTION("/h");
137 const QLatin1String
HELP4_OPTION("--help");
138 const QLatin1String
VERSION_OPTION("-version");
139 const QLatin1String
NO_SPLASH_OPTION("-no-splash");
140 const QLatin1String
CLIENT_OPTION("-client");
141 const QLatin1String
CONFIG_OPTION("-D");
142 const QLatin1String
RESET_OPTION("-reset");
143 const QLatin1String
CONFIG_FILE_OPTION("-config-file");
144 const QLatin1String
EXIT_AFTER_CONFIG_OPTION("-exit-after-config");
145 const QLatin1String
LOG_FILE_OPTION("-log");
147 // Helpers for displaying messages. Note that there is no console on Windows.
148 void displayHelpText(QString t
)
151 // No console on Windows. (???)
152 // TODO there is a console on windows and popups are not always desired
153 t
.replace(QLatin1Char('&'), QLatin1String("&"));
154 t
.replace(QLatin1Char('<'), QLatin1String("<"));
155 t
.replace(QLatin1Char('>'), QLatin1String(">"));
156 t
.insert(0, QLatin1String("<html><pre>"));
157 t
.append(QLatin1String("</pre></html>"));
158 QMessageBox::information(0, APP_NAME
, t
);
160 qWarning("%s", qPrintable(t
));
164 void displayError(const QString
&t
)
167 // No console on Windows. (???)
168 // TODO there is a console on windows and popups are not always desired
169 QMessageBox::critical(0, APP_NAME
, t
);
171 qCritical("%s", qPrintable(t
));
175 void printVersion(const ExtensionSystem::PluginSpec
*corePlugin
, const ExtensionSystem::PluginManager
&pm
)
178 QTextStream
str(&version
);
180 str
<< '\n' << APP_NAME
<< ' ' << corePlugin
->version() << " based on Qt " << qVersion() << "\n\n";
181 pm
.formatPluginVersions(str
);
182 str
<< '\n' << corePlugin
->copyright() << '\n';
183 displayHelpText(version
);
186 void printHelp(const QString
&appExecName
, const ExtensionSystem::PluginManager
&pm
)
189 QTextStream
str(&help
);
191 str
<< "Usage: " << appExecName
<< fixedOptionsC
;
192 ExtensionSystem::PluginManager::formatOptions(str
, OptionIndent
, DescriptionIndent
);
193 pm
.formatPluginOptions(str
, OptionIndent
, DescriptionIndent
);
194 displayHelpText(help
);
197 inline QString
msgCoreLoadFailure(const QString
&reason
)
199 return QCoreApplication::translate("Application", "Failed to load core plug-in, reason is: %1").arg(reason
);
202 inline QString
msgSendArgumentFailed()
204 return QCoreApplication::translate("Application",
205 "Unable to send command line arguments to the already running instance. It appears to be not responding.");
208 inline QString
msgLogfileOpenFailed(const QString
&fileName
)
210 return QCoreApplication::translate("Application", "Failed to open log file %1").arg(fileName
);
213 // Prepare a remote argument: If it is a relative file, add the current directory
214 // since the the central instance might be running in a different directory.
215 inline QString
prepareRemoteArgument(const QString
&arg
)
222 if (fi
.isRelative()) {
223 return fi
.absoluteFilePath();
228 // Send the arguments to an already running instance of application
229 bool sendArguments(SharedTools::QtSingleApplication
&app
, const QStringList
&arguments
)
231 if (!arguments
.empty()) {
232 // Send off arguments
233 const QStringList::const_iterator acend
= arguments
.constEnd();
234 for (QStringList::const_iterator it
= arguments
.constBegin(); it
!= acend
; ++it
) {
235 if (!app
.sendMessage(prepareRemoteArgument(*it
))) {
236 displayError(msgSendArgumentFailed());
241 // Special empty argument means: Show and raise (the slot just needs to be triggered)
242 if (!app
.sendMessage(QString())) {
243 displayError(msgSendArgumentFailed());
252 // increase the number of file that can be opened in application
254 getrlimit(RLIMIT_NOFILE
, &rl
);
255 rl
.rlim_cur
= rl
.rlim_max
;
256 setrlimit(RLIMIT_NOFILE
, &rl
);
259 QApplication::setAttribute(Qt::AA_X11InitThreads
, true);
261 // protect QQuickWidget from native widgets like GLC ModelView
262 // TODO revisit this...
263 QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings
, true);
266 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
267 // see https://doc-snapshots.qt.io/qt5-5.6/highdpi.html
268 qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1");
270 // see http://doc.qt.io/qt-5/highdpi.html
271 qputenv("QT_DEVICE_PIXEL_RATIO", "auto");
275 // Force "basic" render loop
276 // Only Mac uses "threaded" by default and that mode currently does not work well with OSGViewport
277 qputenv("QSG_RENDER_LOOP", "basic");
280 QSurfaceFormat format
= QSurfaceFormat::defaultFormat();
281 format
.setSwapInterval(0);
282 QSurfaceFormat::setDefaultFormat(format
);
284 // see https://bugreports.qt.io/browse/QTBUG-40332
285 int timeout
= std::numeric_limits
<int>::max();
286 qputenv("QT_BEARER_POLL_TIMEOUT", QString::number(timeout
).toLatin1());
289 static FileLogger
*logger
= NULL
;
291 void mainMessageOutput(QtMsgType type
, const QMessageLogContext
&context
, const QString
&msg
)
293 logger
->log(type
, context
, msg
);
296 Q_DECLARE_METATYPE(QtMsgType
)
298 void logInit(QString
&fileName
)
300 qRegisterMetaType
<QtMsgType
>();
301 logger
= new FileLogger(fileName
);
302 if (!logger
->start()) {
305 displayError(msgLogfileOpenFailed(fileName
));
308 qInstallMessageHandler(mainMessageOutput
);
316 qInstallMessageHandler(0);
323 AppOptions appOptions
;
325 appOptions
.insert(HELP1_OPTION
, false);
326 appOptions
.insert(HELP2_OPTION
, false);
327 appOptions
.insert(HELP3_OPTION
, false);
328 appOptions
.insert(HELP4_OPTION
, false);
329 appOptions
.insert(VERSION_OPTION
, false);
330 appOptions
.insert(NO_SPLASH_OPTION
, false);
331 appOptions
.insert(LOG_FILE_OPTION
, true);
332 appOptions
.insert(CLIENT_OPTION
, false);
333 appOptions
.insert(CONFIG_OPTION
, true);
334 appOptions
.insert(RESET_OPTION
, false);
335 appOptions
.insert(CONFIG_FILE_OPTION
, true);
336 appOptions
.insert(EXIT_AFTER_CONFIG_OPTION
, false);
340 AppOptionValues
parseCommandLine(SharedTools::QtSingleApplication
&app
,
341 ExtensionSystem::PluginManager
&pluginManager
, QString
&errorMessage
)
343 AppOptionValues appOptionValues
;
344 const QStringList arguments
= app
.arguments();
346 if (arguments
.size() > 1) {
347 AppOptions appOptions
= options();
348 pluginManager
.parseOptions(arguments
, appOptions
, &appOptionValues
, &errorMessage
);
350 return appOptionValues
;
353 void loadTranslators(QString language
, QTranslator
&translator
, QTranslator
&qtTranslator
)
355 const QString
&creatorTrPath
= Utils::GetDataPath() + QLatin1String("translations");
357 if (translator
.load(QLatin1String("gcs_") + language
, creatorTrPath
)) {
358 // Install gcs_xx.qm translation file
359 QCoreApplication::installTranslator(&translator
);
361 const QString
&qtTrPath
= QLibraryInfo::location(QLibraryInfo::TranslationsPath
);
362 const QString
&qtTrFile
= QLatin1String("qt_") + language
;
363 // Binary installer puts Qt tr files into creatorTrPath
364 if (qtTranslator
.load(qtTrFile
, qtTrPath
) || qtTranslator
.load(qtTrFile
, creatorTrPath
)) {
365 // Install main qt_xx.qm translation file
366 QCoreApplication::installTranslator(&qtTranslator
);
369 // unload(), no gcs translation found
370 translator
.load(QString());
374 int runApplication(int argc
, char * *argv
)
380 // create application
381 SharedTools::QtSingleApplication
app(APP_NAME
, argc
, argv
);
383 QCoreApplication::setApplicationName(APP_NAME
);
384 QCoreApplication::setOrganizationName(ORG_NAME
);
386 // initialize the plugin manager
387 ExtensionSystem::PluginManager pluginManager
;
388 pluginManager
.setFileExtension(QLatin1String("pluginspec"));
389 pluginManager
.setPluginPaths(Utils::GetPluginPaths());
391 // parse command line
392 qDebug() << "main - command line" << app
.arguments();
393 QString errorMessage
;
394 AppOptionValues appOptionValues
= parseCommandLine(app
, pluginManager
, errorMessage
);
395 if (!errorMessage
.isEmpty()) {
396 // this will display two popups : one error popup + one usage string popup
397 // TODO merge two popups into one.
398 displayError(errorMessage
);
399 printHelp(QFileInfo(app
.applicationFilePath()).baseName(), pluginManager
);
403 // start logging to file if requested
404 if (appOptionValues
.contains(LOG_FILE_OPTION
)) {
405 QString logFileName
= appOptionValues
.value(LOG_FILE_OPTION
);
406 logInit(logFileName
);
407 // relog command line arguments for the benefit of the file logger...
408 qDebug() << "main - command line" << app
.arguments();
412 Utils::initSettings(appOptionValues
.value(CONFIG_FILE_OPTION
));
414 // load user settings
416 qDebug() << "main - loading user settings from" << settings
.fileName();
418 // need to reset user settings?
419 if (settings
.allKeys().isEmpty() || appOptionValues
.contains(RESET_OPTION
)) {
420 qDebug() << "main - resetting user settings";
421 Utils::resetToFactoryDefaults(settings
);
423 Utils::mergeFactoryDefaults(settings
);
425 // override settings with command line provided values
426 // take notice that the overridden values will be saved in the user settings and will continue to be effective
427 // in subsequent GCS runs
428 Utils::overrideSettings(settings
, argc
, argv
);
430 // initialize GCS locale
431 // use the value defined by the General/Locale setting or default to system Locale.
432 // the General/Locale setting is not available in the Options dialog, it is a hidden setting but can still be changed:
433 // - through the command line
434 // - editing the factory defaults XML file before 1st launch
435 // - editing the user XML file
436 QString localeName
= settings
.value("General/Locale", QLocale::system().name()).toString();
437 QLocale::setDefault(localeName
);
440 qDebug() << "main - system locale:" << QLocale::system().name();
441 qDebug() << "main - GCS locale:" << QLocale().name();
443 // load translation file
444 // the language used is defined by the General/OverrideLanguage setting (defaults to GCS locale)
445 // if the translation file for the given language is not found, GCS will default to built in English.
446 QString language
= settings
.value("General/OverrideLanguage", localeName
).toString();
447 qDebug() << "main - language:" << language
;
448 QTranslator translator
;
449 QTranslator qtTranslator
;
450 loadTranslators(language
, translator
, qtTranslator
);
452 app
.setProperty("qtc_locale", localeName
); // Do we need this?
454 if (appOptionValues
.contains(EXIT_AFTER_CONFIG_OPTION
)) {
455 qDebug() << "main - exiting after config!";
459 // open the splash screen
460 GCSSplashScreen
*splash
= 0;
461 if (!appOptionValues
.contains(NO_SPLASH_OPTION
)) {
462 splash
= new GCSSplashScreen();
464 splash
->showProgressMessage(QObject::tr("Application starting..."));
466 // connect to track progress of plugin manager
467 QObject::connect(&pluginManager
, SIGNAL(pluginAboutToBeLoaded(ExtensionSystem::PluginSpec
*)), splash
,
468 SLOT(showPluginLoadingProgress(ExtensionSystem::PluginSpec
*)));
471 // find and load core plugin
472 const PluginSpecSet plugins
= pluginManager
.plugins();
473 ExtensionSystem::PluginSpec
*coreplugin
= 0;
474 foreach(ExtensionSystem::PluginSpec
* spec
, plugins
) {
475 if (spec
->name() == CORE_PLUGIN_NAME
) {
481 QString nativePaths
= QDir::toNativeSeparators(Utils::GetPluginPaths().join(QLatin1String(",")));
482 const QString reason
= QCoreApplication::translate("Application", "Could not find '%1.pluginspec' in %2")
483 .arg(CORE_PLUGIN_NAME
).arg(nativePaths
);
484 displayError(msgCoreLoadFailure(reason
));
487 if (coreplugin
->hasError()) {
488 displayError(msgCoreLoadFailure(coreplugin
->errorString()));
492 if (appOptionValues
.contains(VERSION_OPTION
)) {
493 printVersion(coreplugin
, pluginManager
);
496 if (appOptionValues
.contains(HELP1_OPTION
) || appOptionValues
.contains(HELP2_OPTION
)
497 || appOptionValues
.contains(HELP3_OPTION
) || appOptionValues
.contains(HELP4_OPTION
)) {
498 printHelp(QFileInfo(app
.applicationFilePath()).baseName(), pluginManager
);
502 const bool isFirstInstance
= !app
.isRunning();
503 if (!isFirstInstance
&& appOptionValues
.contains(CLIENT_OPTION
)) {
504 return sendArguments(app
, pluginManager
.arguments()) ? 0 : -1;
507 pluginManager
.loadPlugins();
509 if (coreplugin
->hasError()) {
510 displayError(msgCoreLoadFailure(coreplugin
->errorString()));
515 foreach(ExtensionSystem::PluginSpec
* p
, pluginManager
.plugins()) {
517 errors
.append(p
->errorString());
520 if (!errors
.isEmpty()) {
521 QMessageBox::warning(0,
522 QCoreApplication::translate("Application", "%1 - Plugin loader messages").arg(GCS_BIG_NAME
),
523 errors
.join(QString::fromLatin1("\n\n")));
526 if (isFirstInstance
) {
527 // Set up lock and remote arguments for the first instance only.
528 // Silently fallback to unconnected instances for any subsequent instances.
530 QObject::connect(&app
, SIGNAL(messageReceived(QString
)), coreplugin
->plugin(), SLOT(remoteArgument(QString
)));
532 QObject::connect(&app
, SIGNAL(fileOpenRequest(QString
)), coreplugin
->plugin(), SLOT(remoteArgument(QString
)));
534 // Do this after the event loop has started
535 QTimer::singleShot(100, &pluginManager
, SLOT(startTests()));
538 // close and delete splash
543 qDebug() << "main - starting GCS took" << timer
.elapsed() << "ms";
544 int ret
= app
.exec();
545 qDebug() << "main - GCS ran for" << timer
.elapsed() << "ms";
550 int main(int argc
, char * *argv
)
555 int ret
= runApplication(argc
, argv
);
557 // close log file if needed