2 ******************************************************************************
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * Parts by Nokia Corporation (qt-info@nokia.com) Copyright (C) 2009.
8 * @see The GNU Public License (GPL) Version 3
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 The GCS locale is set to the system locale by default unless the "hidden" setting General/Locale has a value.
31 The user can not change General/Locale from the Options dialog.
33 The GCS language will default to the GCS locale unless the General/OverrideLanguage has a value.
34 The user can change General/OverrideLanguage to any available language from the Options dialog.
36 Both General/Locale and General/OverrideLanguage can be set from the command line or through the the factory defaults file.
38 The -D option is used to permanently set a user setting.
40 The -reset switch will clear all the user settings and will trigger a reload of the factory defaults.
42 You can combine it with the -config-file=<file> command line argument to quickly switch between multiple settings files.
45 openpilotgcs -reset -config-file ./MyOpenPilotGCS.xml
48 Relative paths are relative to <install dir>/share/openpilotgcs/default_configurations/
50 The specified file will be used to load the factory defaults from but only when the user settings are empty.
51 If the user settings are not empty the file will not be used.
52 This switch is useful on the 1st run when the user settings are empty or in combination with -reset.
55 Quickly switch configurations
58 openpilotgcs -reset -config-file <relative or absolute path>
61 Configuring GCS from installer
63 The -D option is used to permanently set a user setting.
65 If the user chooses to start GCS at the end of the installer:
68 openpilotgcs -D General/OverrideLanguage=de
71 If the user chooses not to start GCS at the end of the installer, you still need to configure GCS.
72 In that case you can use -exit-after-config
75 openpilotgcs -D General/OverrideLanguage=de -exit-after-config
80 #include "qtsingleapplication.h"
81 #include "utils/xmlconfig.h"
82 #include "utils/pathutils.h"
83 #include "gcssplashscreen.h"
85 #include <extensionsystem/pluginmanager.h>
86 #include <extensionsystem/pluginspec.h>
87 #include <extensionsystem/iplugin.h>
89 #include <QtCore/QDir>
90 #include <QtCore/QFile>
91 #include <QtCore/QElapsedTimer>
92 #include <QtCore/QTextStream>
93 #include <QtCore/QFileInfo>
94 #include <QtCore/QDebug>
95 #include <QtCore/QTimer>
96 #include <QtCore/QLibraryInfo>
97 #include <QtCore/QTranslator>
98 #include <QtCore/QSettings>
99 #include <QtCore/QVariant>
101 #include <QMessageBox>
102 #include <QtWidgets/QApplication>
103 #include <QMainWindow>
104 #include <QSplashScreen>
107 typedef QList
<ExtensionSystem::PluginSpec
*> PluginSpecSet
;
108 typedef QMap
<QString
, bool> AppOptions
;
109 typedef QMap
<QString
, QString
> AppOptionValues
;
111 const int OptionIndent
= 4;
112 const int DescriptionIndent
= 24;
114 const QLatin1String
APP_NAME(GCS_NAME
);
115 const QLatin1String
ORG_NAME("OpenPilot");
117 const QLatin1String
CORE_PLUGIN_NAME("Core");
119 const char *DEFAULT_CONFIG_FILENAME
= "OpenPilotGCS.xml";
121 const char *fixedOptionsC
= " [OPTION]... [FILE]...\n"
123 " -help Display this help\n"
124 " -version Display application version\n"
125 " -no-splash Don't display splash screen\n"
126 " -log <file> Log to specified file\n"
127 " -client Attempt to connect to already running instance\n"
128 " -D <key>=<value> Permanently set a user setting, e.g: -D General/OverrideLanguage=de\n"
129 " -reset Reset user settings to factory defaults.\n"
130 " -config-file <file> Specify alternate factory defaults settings file (used with -reset)\n"
131 " -exit-after-config Exit after manipulating configuration settings\n";
133 const QLatin1String
HELP1_OPTION("-h");
134 const QLatin1String
HELP2_OPTION("-help");
135 const QLatin1String
HELP3_OPTION("/h");
136 const QLatin1String
HELP4_OPTION("--help");
137 const QLatin1String
VERSION_OPTION("-version");
138 const QLatin1String
NO_SPLASH_OPTION("-no-splash");
139 const QLatin1String
CLIENT_OPTION("-client");
140 const QLatin1String
CONFIG_OPTION("-D");
141 const QLatin1String
RESET_OPTION("-reset");
142 const QLatin1String
CONFIG_FILE_OPTION("-config-file");
143 const QLatin1String
EXIT_AFTER_CONFIG_OPTION("-exit-after-config");
144 const QLatin1String
LOG_FILE_OPTION("-log");
146 // Helpers for displaying messages. Note that there is no console on Windows.
147 void displayHelpText(QString t
)
150 // No console on Windows. (???)
151 // TODO there is a console on windows and popups are not always desired
152 t
.replace(QLatin1Char('&'), QLatin1String("&"));
153 t
.replace(QLatin1Char('<'), QLatin1String("<"));
154 t
.replace(QLatin1Char('>'), QLatin1String(">"));
155 t
.insert(0, QLatin1String("<html><pre>"));
156 t
.append(QLatin1String("</pre></html>"));
157 QMessageBox::information(0, APP_NAME
, t
);
159 qWarning("%s", qPrintable(t
));
163 void displayError(const QString
&t
)
166 // No console on Windows. (???)
167 // TODO there is a console on windows and popups are not always desired
168 QMessageBox::critical(0, APP_NAME
, t
);
170 qCritical("%s", qPrintable(t
));
174 void printVersion(const ExtensionSystem::PluginSpec
*corePlugin
, const ExtensionSystem::PluginManager
&pm
)
177 QTextStream
str(&version
);
179 str
<< '\n' << APP_NAME
<< ' ' << corePlugin
->version() << " based on Qt " << qVersion() << "\n\n";
180 pm
.formatPluginVersions(str
);
181 str
<< '\n' << corePlugin
->copyright() << '\n';
182 displayHelpText(version
);
185 void printHelp(const QString
&appExecName
, const ExtensionSystem::PluginManager
&pm
)
188 QTextStream
str(&help
);
190 str
<< "Usage: " << appExecName
<< fixedOptionsC
;
191 ExtensionSystem::PluginManager::formatOptions(str
, OptionIndent
, DescriptionIndent
);
192 pm
.formatPluginOptions(str
, OptionIndent
, DescriptionIndent
);
193 displayHelpText(help
);
196 inline QString
msgCoreLoadFailure(const QString
&reason
)
198 return QCoreApplication::translate("Application", "Failed to load core plug-in, reason is: %1").arg(reason
);
201 inline QString
msgSendArgumentFailed()
203 return QCoreApplication::translate("Application",
204 "Unable to send command line arguments to the already running instance. It appears to be not responding.");
207 // Prepare a remote argument: If it is a relative file, add the current directory
208 // since the the central instance might be running in a different directory.
209 inline QString
prepareRemoteArgument(const QString
&arg
)
216 if (fi
.isRelative()) {
217 return fi
.absoluteFilePath();
222 // Send the arguments to an already running instance of application
223 bool sendArguments(SharedTools::QtSingleApplication
&app
, const QStringList
&arguments
)
225 if (!arguments
.empty()) {
226 // Send off arguments
227 const QStringList::const_iterator acend
= arguments
.constEnd();
228 for (QStringList::const_iterator it
= arguments
.constBegin(); it
!= acend
; ++it
) {
229 if (!app
.sendMessage(prepareRemoteArgument(*it
))) {
230 displayError(msgSendArgumentFailed());
235 // Special empty argument means: Show and raise (the slot just needs to be triggered)
236 if (!app
.sendMessage(QString())) {
237 displayError(msgSendArgumentFailed());
246 // increase the number of file that can be opened in application
248 getrlimit(RLIMIT_NOFILE
, &rl
);
249 rl
.rlim_cur
= rl
.rlim_max
;
250 setrlimit(RLIMIT_NOFILE
, &rl
);
251 QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings
, true);
254 QApplication::setAttribute(Qt::AA_X11InitThreads
, true);
258 static QTextStream
*logStream
;
260 void mainMessageOutput(QtMsgType type
, const QMessageLogContext
&context
, const QString
&msg
)
264 QTextStream
&out
= *logStream
;
266 // logStream << QTime::currentTime().toString("hh:mm:ss.zzz ");
287 void logInit(QString fileName
)
289 QFile
*file
= new QFile(fileName
);
291 if (file
->open(QIODevice::WriteOnly
| QIODevice::Text
)) {
292 logStream
= new QTextStream(file
);
293 qInstallMessageHandler(mainMessageOutput
);
299 inline QStringList
getPluginPaths()
303 QString pluginPath
= QApplication::applicationDirPath();
304 pluginPath
+= QLatin1Char('/');
305 pluginPath
+= QLatin1String(PLUGIN_REL_PATH
);
306 rc
.push_back(pluginPath
);
313 AppOptions appOptions
;
315 appOptions
.insert(HELP1_OPTION
, false);
316 appOptions
.insert(HELP2_OPTION
, false);
317 appOptions
.insert(HELP3_OPTION
, false);
318 appOptions
.insert(HELP4_OPTION
, false);
319 appOptions
.insert(VERSION_OPTION
, false);
320 appOptions
.insert(NO_SPLASH_OPTION
, false);
321 appOptions
.insert(LOG_FILE_OPTION
, true);
322 appOptions
.insert(CLIENT_OPTION
, false);
323 appOptions
.insert(CONFIG_OPTION
, true);
324 appOptions
.insert(RESET_OPTION
, false);
325 appOptions
.insert(CONFIG_FILE_OPTION
, true);
326 appOptions
.insert(EXIT_AFTER_CONFIG_OPTION
, false);
330 AppOptionValues
parseCommandLine(SharedTools::QtSingleApplication
&app
,
331 ExtensionSystem::PluginManager
&pluginManager
, QString
&errorMessage
)
333 AppOptionValues appOptionValues
;
334 const QStringList arguments
= app
.arguments();
336 if (arguments
.size() > 1) {
337 AppOptions appOptions
= options();
338 pluginManager
.parseOptions(arguments
, appOptions
, &appOptionValues
, &errorMessage
);
340 return appOptionValues
;
343 void loadFactoryDefaults(QSettings
&settings
, AppOptionValues
&appOptionValues
)
345 QDir
directory(Utils::PathUtils().GetDataPath() + QString("default_configurations"));
347 qDebug() << "Looking for factory defaults configuration files in:" << directory
.absolutePath();
351 // check if command line option -config-file contains a file name
352 QString commandLine
= appOptionValues
.value(CONFIG_FILE_OPTION
);
353 if (!commandLine
.isEmpty()) {
354 QFileInfo
fi(commandLine
);
355 if (fi
.isRelative()) {
356 // file name specified on command line has a relative path
357 commandLine
= directory
.absolutePath() + QDir::separator() + commandLine
;
359 if (QFile::exists(commandLine
)) {
360 fileName
= commandLine
;
361 qDebug() << "Configuration file" << fileName
<< "specified on command line will be loaded.";
363 qWarning() << "Configuration file" << commandLine
<< "specified on command line does not exist.";
367 if (fileName
.isEmpty()) {
368 // check default file
369 if (QFile::exists(directory
.absolutePath() + QDir::separator() + DEFAULT_CONFIG_FILENAME
)) {
370 // use default file name
371 fileName
= directory
.absolutePath() + QDir::separator() + DEFAULT_CONFIG_FILENAME
;
372 qDebug() << "Default configuration file" << fileName
<< "will be loaded.";
374 qWarning() << "No default configuration file found in" << directory
.absolutePath();
378 if (fileName
.isEmpty()) {
379 // TODO should we exit violently?
380 qCritical() << "No default configuration file found!";
384 // create settings from file
385 QSettings
qs(fileName
, XmlConfig::XmlSettingsFormat
);
387 // transfer loaded settings to application settings
388 QStringList keys
= qs
.allKeys();
389 foreach(QString key
, keys
) {
390 settings
.setValue(key
, qs
.value(key
));
393 qDebug() << "Configuration file" << fileName
<< "was loaded.";
396 void overrideSettings(QSettings
&settings
, int argc
, char * *argv
)
398 // Options like -D My/setting=test
399 QRegExp
rx("([^=]+)=(.*)");
401 for (int i
= 0; i
< argc
; ++i
) {
402 if (CONFIG_OPTION
== QString(argv
[i
])) {
403 if (rx
.indexIn(argv
[++i
]) > -1) {
404 QString key
= rx
.cap(1);
405 QString value
= rx
.cap(2);
406 qDebug() << "User setting" << key
<< "set to value" << value
;
407 settings
.setValue(key
, value
);
415 void loadTranslators(QString language
, QTranslator
&translator
, QTranslator
&qtTranslator
)
417 const QString
&creatorTrPath
= Utils::PathUtils().GetDataPath() + QLatin1String("translations");
419 if (translator
.load(QLatin1String("openpilotgcs_") + language
, creatorTrPath
)) {
420 const QString
&qtTrPath
= QLibraryInfo::location(QLibraryInfo::TranslationsPath
);
421 const QString
&qtTrFile
= QLatin1String("qt_") + language
;
422 // Binary installer puts Qt tr files into creatorTrPath
423 if (qtTranslator
.load(qtTrFile
, qtTrPath
) || qtTranslator
.load(qtTrFile
, creatorTrPath
)) {
424 QCoreApplication::installTranslator(&translator
);
425 QCoreApplication::installTranslator(&qtTranslator
);
428 translator
.load(QString());
432 } // namespace anonymous
434 int main(int argc
, char * *argv
)
443 // create application
444 SharedTools::QtSingleApplication
app(APP_NAME
, argc
, argv
);
446 QCoreApplication::setApplicationName(APP_NAME
);
447 QCoreApplication::setOrganizationName(ORG_NAME
);
448 QSettings::setDefaultFormat(XmlConfig::XmlSettingsFormat
);
450 // initialize the plugin manager
451 ExtensionSystem::PluginManager pluginManager
;
452 pluginManager
.setFileExtension(QLatin1String("pluginspec"));
453 pluginManager
.setPluginPaths(getPluginPaths());
455 // parse command line
456 qDebug() << "Command line" << app
.arguments();
457 QString errorMessage
;
458 AppOptionValues appOptionValues
= parseCommandLine(app
, pluginManager
, errorMessage
);
459 if (!errorMessage
.isEmpty()) {
460 // this will display two popups : one error popup + one usage string popup
461 // TODO merge two popups into one.
462 displayError(errorMessage
);
463 printHelp(QFileInfo(app
.applicationFilePath()).baseName(), pluginManager
);
467 if (appOptionValues
.contains(LOG_FILE_OPTION
)) {
468 QString logFileName
= appOptionValues
.value(LOG_FILE_OPTION
);
469 logInit(logFileName
);
472 // load user settings
473 // Must be done before any QSettings class is created
474 // keep this in sync with the MainWindow ctor in coreplugin/mainwindow.cpp
475 QString settingsPath
= Utils::PathUtils().GetDataPath();
476 qDebug() << "Loading system settings from" << settingsPath
;
477 QSettings::setPath(XmlConfig::XmlSettingsFormat
, QSettings::SystemScope
, settingsPath
);
479 qDebug() << "Loading user settings from" << settings
.fileName();
481 // need to reset all user settings?
482 if (appOptionValues
.contains(RESET_OPTION
)) {
483 qDebug() << "Resetting user settings!";
487 // check if we have user settings
488 if (!settings
.allKeys().count()) {
489 // no user settings, load the factory defaults
490 qDebug() << "No user settings found, loading factory defaults...";
491 loadFactoryDefaults(settings
, appOptionValues
);
494 // override settings with command line provided values
495 // take notice that the overridden values will be saved in the user settings and will continue to be effective
496 // in subsequent GCS runs
497 overrideSettings(settings
, argc
, argv
);
499 // initialize GCS locale
500 // use the value defined by the General/Locale setting or default to system Locale.
501 // the General/Locale setting is not available in the Options dialog, it is a hidden setting but can still be changed:
502 // - through the command line
503 // - editing the factory defaults XML file before 1st launch
504 // - editing the user XML file
505 QString localeName
= settings
.value("General/Locale", QLocale::system().name()).toString();
506 QLocale::setDefault(localeName
);
509 qDebug() << "main - system locale:" << QLocale::system().name();
510 qDebug() << "main - GCS locale:" << QLocale().name();
512 // load translation file
513 // the language used is defined by the General/OverrideLanguage setting (defaults to GCS locale)
514 // if the translation file for the given language is not found, GCS will default to built in English.
515 QString language
= settings
.value("General/OverrideLanguage", localeName
).toString();
516 qDebug() << "main - language:" << language
;
517 QTranslator translator
;
518 QTranslator qtTranslator
;
519 loadTranslators(language
, translator
, qtTranslator
);
521 app
.setProperty("qtc_locale", localeName
); // Do we need this?
523 if (appOptionValues
.contains(EXIT_AFTER_CONFIG_OPTION
)) {
524 qDebug() << "main - exiting after config!";
528 // open the splash screen
529 GCSSplashScreen
*splash
= 0;
530 if (!appOptionValues
.contains(NO_SPLASH_OPTION
)) {
531 splash
= new GCSSplashScreen();
533 splash
->showProgressMessage(QObject::tr("Application starting..."));
535 // connect to track progress of plugin manager
536 QObject::connect(&pluginManager
, SIGNAL(pluginAboutToBeLoaded(ExtensionSystem::PluginSpec
*)), splash
,
537 SLOT(showPluginLoadingProgress(ExtensionSystem::PluginSpec
*)));
540 // find and load core plugin
541 const PluginSpecSet plugins
= pluginManager
.plugins();
542 ExtensionSystem::PluginSpec
*coreplugin
= 0;
543 foreach(ExtensionSystem::PluginSpec
* spec
, plugins
) {
544 if (spec
->name() == CORE_PLUGIN_NAME
) {
550 QString nativePaths
= QDir::toNativeSeparators(getPluginPaths().join(QLatin1String(",")));
551 const QString reason
= QCoreApplication::translate("Application", "Could not find 'Core.pluginspec' in %1").arg(
553 displayError(msgCoreLoadFailure(reason
));
556 if (coreplugin
->hasError()) {
557 displayError(msgCoreLoadFailure(coreplugin
->errorString()));
561 if (appOptionValues
.contains(VERSION_OPTION
)) {
562 printVersion(coreplugin
, pluginManager
);
565 if (appOptionValues
.contains(HELP1_OPTION
) || appOptionValues
.contains(HELP2_OPTION
)
566 || appOptionValues
.contains(HELP3_OPTION
) || appOptionValues
.contains(HELP4_OPTION
)) {
567 printHelp(QFileInfo(app
.applicationFilePath()).baseName(), pluginManager
);
571 const bool isFirstInstance
= !app
.isRunning();
572 if (!isFirstInstance
&& appOptionValues
.contains(CLIENT_OPTION
)) {
573 return sendArguments(app
, pluginManager
.arguments()) ? 0 : -1;
576 pluginManager
.loadPlugins();
578 if (coreplugin
->hasError()) {
579 displayError(msgCoreLoadFailure(coreplugin
->errorString()));
585 foreach(ExtensionSystem::PluginSpec
* p
, pluginManager
.plugins()) {
587 errors
.append(p
->errorString());
590 if (!errors
.isEmpty()) {
591 QMessageBox::warning(0,
592 QCoreApplication::translate("Application", "OpenPilot GCS - Plugin loader messages"),
593 errors
.join(QString::fromLatin1("\n\n")));
597 if (isFirstInstance
) {
598 // Set up lock and remote arguments for the first instance only.
599 // Silently fallback to unconnected instances for any subsequent instances.
601 QObject::connect(&app
, SIGNAL(messageReceived(QString
)), coreplugin
->plugin(), SLOT(remoteArgument(QString
)));
603 QObject::connect(&app
, SIGNAL(fileOpenRequest(QString
)), coreplugin
->plugin(), SLOT(remoteArgument(QString
)));
605 // Do this after the event loop has started
606 QTimer::singleShot(100, &pluginManager
, SLOT(startTests()));
609 // close and delete splash
614 qDebug() << "main - main took" << timer
.elapsed() << "ms";
616 int ret
= app
.exec();
618 qDebug() << "main - GCS ran for" << timer
.elapsed() << "ms";