3#include <QtCore/QEvent>
5#include <QtCore/QMetaMethod>
6#include <QtCore/QMetaObject>
7#include <QtCore/QRegularExpression>
8#include <QtCore/private/qthread_p.h>
9#include <QtGui/QFontDatabase>
11#include <QtQml/QQmlApplicationEngine>
12#include <QtQml/QQmlContext>
13#include <QtQuick/QQuickImageProvider>
14#include <QtQuick/QQuickWindow>
15#include <QtQuickControls2/QQuickStyle>
16#include <QtSvg/QSvgRenderer>
43#include "qgc_version.h"
45#ifndef QGC_NO_SERIAL_LINK
53 : QGuiApplication(argc, argv),
54 _runningUnitTests(cli.runningUnitTests),
55 _simpleBootTest(cli.simpleBootTest),
56 _fakeMobile(cli.fakeMobile),
57 _logOutput(cli.logOutput),
58 _systemId(cli.systemId.value_or(0))
60 _msecsElapsedTime.start();
65 bool fClearSettingsOptions = cli.clearSettingsOptions;
66 const bool fClearCache = cli.clearCache;
67 const QString loggingOptions = cli.loggingOptions.value_or(QString(
""));
70 _missingParamsDelayedDisplayTimer.setSingleShot(
true);
71 _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout);
72 (void) connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout,
this, &QGCApplication::_missingParamsDisplay);
75 QString applicationName;
76 if (_runningUnitTests || _simpleBootTest) {
80 if (!cli.unitTests.isEmpty()) {
81 applicationName = QStringLiteral(
"%1_unittest_%2").arg(QGC_APP_NAME, cli.unitTests.first());
84 QStringLiteral(
"%1_unittest_%2").arg(QGC_APP_NAME).arg(QCoreApplication::applicationPid());
90 applicationName = QStringLiteral(
"%1 Daily").arg(QGC_APP_NAME);
92 applicationName = QGC_APP_NAME;
95 setApplicationName(applicationName);
96 setDesktopFileName(QGC_PACKAGE_NAME);
97 setOrganizationName(QGC_ORG_NAME);
98 setOrganizationDomain(QGC_ORG_DOMAIN);
99 setApplicationVersion(QString(QGC_APP_VERSION_STR));
102 QSettings::setDefaultFormat(QSettings::IniFormat);
104 qCDebug(QGCApplicationLog) <<
"Settings location" << settings.fileName()
105 <<
"Is writable?:" << settings.isWritable();
107 if (!settings.isWritable()) {
108 qCWarning(QGCApplicationLog) <<
"Setings location is not writable";
114 if (_runningUnitTests || _simpleBootTest) {
116 fClearSettingsOptions =
true;
119 if (fClearSettingsOptions) {
125 paramDir.removeRecursively();
126 paramDir.mkpath(paramDir.absolutePath());
130 if (settings.contains(_settingsVersionKey)) {
131 if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) {
133 _settingsUpgraded =
true;
137 settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
141 dir.removeRecursively();
142 QFile parameter(cachedParameterMetaDataFile());
144 QFile airframe(cachedAirframeMetaDataFile());
148 const QString metaDataCachePath =
149 QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral(
"/ParameterMetaData");
150 QDir(metaDataCachePath).removeRecursively();
157 if (_runningUnitTests) {
167 QSvgRenderer::setDefaultOptions(QtSvg::Tiny12FeaturesOnly);
169#ifndef QGC_DAILY_BUILD
170 _checkForNewVersion();
176 _locale = QLocale::system();
177 qCDebug(QGCApplicationLog) <<
"System reported locale:" << _locale <<
"; Name" << _locale.name()
178 <<
"; Preffered (used in maps): "
179 << (QLocale::system().uiLanguages().length() > 0 ? QLocale::system().uiLanguages()[0]
182 QLocale::Language possibleLocale = AppSettings::_qLocaleLanguageEarlyAccess();
183 if (possibleLocale != QLocale::AnyLanguage) {
184 _locale = QLocale(possibleLocale);
187 if (_locale == QLocale::Korean) {
188 qCDebug(QGCApplicationLog) <<
"Loading Korean fonts" << _locale.name();
189 if (QFontDatabase::addApplicationFont(
":/fonts/NanumGothic-Regular") < 0) {
190 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/NanumGothic-Regular font";
192 if (QFontDatabase::addApplicationFont(
":/fonts/NanumGothic-Bold") < 0) {
193 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/NanumGothic-Bold font";
196 qCDebug(QGCApplicationLog) <<
"Loading localizations for" << _locale.name();
198 removeTranslator(&_qgcTranslatorSourceCode);
199 removeTranslator(&_qgcTranslatorQtLibs);
200 if (_locale.name() !=
"en_US") {
201 QLocale::setDefault(_locale);
202 if (_qgcTranslatorQtLibs.load(
"qt_" + _locale.name(), QLibraryInfo::path(QLibraryInfo::TranslationsPath))) {
203 installTranslator(&_qgcTranslatorQtLibs);
205 qCWarning(QGCApplicationLog) <<
"Qt lib localization for" << _locale.name() <<
"is not present";
207 if (_qgcTranslatorSourceCode.load(_locale, QLatin1String(
"qgc_source_"),
"",
":/i18n")) {
208 installTranslator(&_qgcTranslatorSourceCode);
210 qCWarning(QGCApplicationLog) <<
"Error loading source localization for" << _locale.name();
215 qCWarning(QGCApplicationLog) <<
"Error loading json localization for" << _locale.name();
220 _qmlAppEngine->retranslate();
232 qCDebug(QGCApplicationLog) <<
"Setting MAVLink System ID to:" << _systemId;
240 if (QFontDatabase::addApplicationFont(
":/fonts/opensans") < 0) {
241 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/opensans font";
244 if (QFontDatabase::addApplicationFont(
":/fonts/opensans-demibold") < 0) {
245 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/opensans-demibold font";
248 if (_simpleBootTest) {
251 const bool videoInitialized = _initVideo();
252 const bool qmlRootLoaded = _initQmlRootWindow();
253 _bootTestPassed = videoInitialized && qmlRootLoaded;
254 }
else if (!_runningUnitTests) {
255 _initForNormalAppBoot();
259bool QGCApplication::_initVideo()
261#ifdef QGC_GST_STREAMING
262 qCDebug(QGCApplicationLog) <<
"Using default graphics API for appsink → VideoOutput video path";
269 _videoManagerInitialized =
true;
270 return initSucceeded;
273bool QGCApplication::_initQmlRootWindow()
275 QQuickStyle::setStyle(
"Basic");
280 QObject::connect(_qmlAppEngine, &QQmlApplicationEngine::objectCreationFailed,
this, QCoreApplication::quit,
281 Qt::QueuedConnection);
285 _qmlAppEngine->addImageProvider(_qgcImageProviderId,
new QGCImageProvider());
297void QGCApplication::_initForNormalAppBoot()
301 (void) _initQmlRootWindow();
313 QUrl windowIcon = QUrl(
"qrc:/res/qgroundcontrol.ico");
314 windowIcon = _qmlAppEngine->interceptUrl(windowIcon, QQmlAbstractUrlInterceptor::UrlString);
316 setWindowIcon(QIcon(
":" + windowIcon.path()));
320 _showErrorsInToolbar =
true;
324#ifndef QGC_NO_SERIAL_LINK
325 if (!_runningUnitTests) {
327 QFile permFile(
"/etc/group");
328 if (permFile.open(QIODevice::ReadOnly)) {
329 while (!permFile.atEnd()) {
330 const QString line = permFile.readLine();
331 if (line.contains(
"dialout") && !line.contains(getenv(
"USER"))) {
334 tr(
"The current user does not have the correct permissions to access serial devices. "
335 "You should also remove modemmanager since it also interferes.<br/><br/>"
336 "If you are using Ubuntu, execute the following commands to fix these issues:<br/>"
337 "<pre>sudo usermod -a -G dialout $USER<br/>"
338 "sudo apt-get remove modemmanager</pre>"));
358 if (_settingsUpgraded) {
359 showAppMessage(tr(
"The format for %1 saved settings has been modified. "
360 "Your saved settings have been reset to defaults.")
361 .arg(applicationName()));
370 const QPair<int, QString> missingParam(componentId, name);
372 if (!_missingParams.contains(missingParam)) {
373 _missingParams.append(missingParam);
375 _missingParamsDelayedDisplayTimer.start();
378void QGCApplication::_missingParamsDisplay()
380 if (_missingParams.isEmpty()) {
385 for (QPair<int, QString>& missingParam : _missingParams) {
386 const QString param = QStringLiteral(
"%1:%2").arg(missingParam.first).arg(missingParam.second);
387 if (params.isEmpty()) {
390 params += QStringLiteral(
", %1").arg(param);
393 _missingParams.clear();
395 showAppMessage(tr(
"Parameters are missing from firmware. You may be running a version of firmware which is not "
396 "fully supported or your firmware has a bug in it. Missing params: %1")
400QObject* QGCApplication::_rootQmlObject()
402 if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) {
403 return _qmlAppEngine->rootObjects()[0];
412 if (message.startsWith(QStringLiteral(
"PreArm")) ||
413 message.startsWith(QStringLiteral(
"preflight"), Qt::CaseInsensitive)) {
417 QObject*
const rootQmlObject = _rootQmlObject();
418 if (rootQmlObject && _showErrorsInToolbar) {
420 QVariant varMessage = QVariant::fromValue(message);
421 QMetaObject::invokeMethod(rootQmlObject,
"showCriticalVehicleMessage", Q_RETURN_ARG(QVariant, varReturn),
422 Q_ARG(QVariant, varMessage));
425 qCDebug(QGCApplicationLog) <<
"QGCApplication::showCriticalVehicleMessage unittest" << message;
427 qCWarning(QGCApplicationLog) <<
"Internal error";
433 const QString dialogTitle = title.isEmpty() ? applicationName() : title;
439 qCDebug(QGCAppMessageLog) <<
"showAppMessage:" << dialogTitle <<
"-" << message;
443 QObject*
const rootQmlObject = _rootQmlObject();
446 QVariant varMessage = QVariant::fromValue(message);
447 QMetaObject::invokeMethod(rootQmlObject,
"_showMessageDialog", Q_RETURN_ARG(QVariant, varReturn),
448 Q_ARG(QVariant, dialogTitle), Q_ARG(QVariant, varMessage));
451 _delayedAppMessages.append(QPair<QString, QString>(dialogTitle, message));
452 QTimer::singleShot(200,
this, &QGCApplication::_showDelayedAppMessages);
458 static QTime lastRebootMessage;
460 const QTime currentTime = QTime::currentTime();
461 const QTime previousTime = lastRebootMessage;
462 lastRebootMessage = currentTime;
464 if (previousTime.isValid() && (previousTime.msecsTo(currentTime) < (60 * 1000 * 2))) {
472void QGCApplication::_showDelayedAppMessages()
474 if (_rootQmlObject()) {
475 for (
const QPair<QString, QString>& appMsg : _delayedAppMessages) {
478 _delayedAppMessages.clear();
480 QTimer::singleShot(200,
this, &QGCApplication::_showDelayedAppMessages);
486 if (!_mainRootWindow) {
487 _mainRootWindow = qobject_cast<QQuickWindow*>(_rootQmlObject());
490 return _mainRootWindow;
495 if (_rootQmlObject()) {
496 QMetaObject::invokeMethod(_rootQmlObject(),
"showVehicleConfig");
502 if (_rootQmlObject()) {
503 QMetaObject::invokeMethod(_rootQmlObject(),
"attemptWindowClose");
507void QGCApplication::_checkForNewVersion()
509 if (_runningUnitTests) {
513 if (!_parseVersionText(applicationVersion(), _majorVersion, _minorVersion, _buildVersion)) {
518 if (!versionCheckFile.isEmpty()) {
521 &QGCApplication::_qgcCurrentStableVersionDownloadComplete);
522 if (!download->
start(versionCheckFile)) {
523 qCDebug(QGCApplicationLog) <<
"Download QGC stable version failed to start" << download->
errorString();
524 download->deleteLater();
529void QGCApplication::_qgcCurrentStableVersionDownloadComplete(
bool success,
const QString& localFile,
530 const QString& errorMsg)
533 QFile versionFile(localFile);
534 if (versionFile.open(QIODevice::ReadOnly)) {
535 QTextStream textStream(&versionFile);
536 const QString version = textStream.readLine();
538 qCDebug(QGCApplicationLog) << version;
540 int majorVersion, minorVersion, buildVersion;
541 if (_parseVersionText(version, majorVersion, minorVersion, buildVersion)) {
542 if (_majorVersion < majorVersion ||
543 ((_majorVersion == majorVersion) && (_minorVersion < minorVersion)) ||
544 ((_majorVersion == majorVersion) && (_minorVersion == minorVersion) &&
545 (_buildVersion < buildVersion))) {
546 showAppMessage(tr(
"There is a newer version of %1 available. You can download it from %2.")
547 .arg(applicationName())
549 tr(
"New Version Available"));
553 }
else if (!errorMsg.isEmpty()) {
554 qCDebug(QGCApplicationLog) <<
"Download QGC stable version failed" << errorMsg;
557 sender()->deleteLater();
560bool QGCApplication::_parseVersionText(
const QString& versionString,
int& majorVersion,
int& minorVersion,
563 static const QRegularExpression regExp(
"v(\\d+)\\.(\\d+)\\.(\\d+)");
564 const QRegularExpressionMatch match = regExp.match(versionString);
565 if (match.hasMatch() && match.lastCapturedIndex() == 3) {
566 majorVersion = match.captured(1).toInt();
567 minorVersion = match.captured(2).toInt();
568 buildVersion = match.captured(3).toInt();
578 const QDir parameterDir = QFileInfo(settings.fileName()).dir();
579 return parameterDir.filePath(QStringLiteral(
"ParameterFactMetaData.json"));
585 const QDir airframeDir = QFileInfo(settings.fileName()).dir();
586 return airframeDir.filePath(QStringLiteral(
"PX4AirframeFactMetaData.xml"));
589int QGCApplication::CompressedSignalList::_signalIndex(
const QMetaMethod& method)
591 if (method.methodType() != QMetaMethod::Signal) {
592 qCWarning(QGCApplicationLog) <<
"Internal error:" << Q_FUNC_INFO <<
"not a signal" << method.methodType();
597 const QMetaObject* metaObject = method.enclosingMetaObject();
598 for (
int i = 0; i <= method.methodIndex(); i++) {
599 if (metaObject->method(i).methodType() != QMetaMethod::Signal) {
608void QGCApplication::CompressedSignalList::add(
const QMetaMethod& method)
610 const QMetaObject* metaObject = method.enclosingMetaObject();
611 const int signalIndex = _signalIndex(method);
613 if (signalIndex != -1 && !contains(metaObject, signalIndex)) {
614 _signalMap[method.enclosingMetaObject()].insert(signalIndex);
618void QGCApplication::CompressedSignalList::remove(
const QMetaMethod& method)
620 const int signalIndex = _signalIndex(method);
621 const QMetaObject*
const metaObject = method.enclosingMetaObject();
623 if (signalIndex != -1 && _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex)) {
624 _signalMap[metaObject].remove(signalIndex);
625 if (_signalMap[metaObject].count() == 0) {
626 _signalMap.remove(metaObject);
631bool QGCApplication::CompressedSignalList::contains(
const QMetaObject* metaObject,
int signalIndex)
633 return _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex);
638 _compressedSignals.add(method);
643 _compressedSignals.remove(method);
648QT_WARNING_DISABLE_DEPRECATED
649bool QGCApplication::compressEvent(QEvent* event, QObject* receiver, QPostEventList* postedEvents)
651 if (
event->type() != QEvent::MetaCall) {
652 return QGuiApplication::compressEvent(
event, receiver, postedEvents);
655 const QMetaCallEvent* mce =
static_cast<QMetaCallEvent*
>(
event);
656 if (!mce->sender() || !_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) {
657 return QGuiApplication::compressEvent(
event, receiver, postedEvents);
661 struct MetaCallHelper :
public QMetaCallEvent {
662 int id()
const {
return d.method_offset_ + d.method_relative_; }
664 const auto methodId = [](
const QMetaCallEvent *e) {
return static_cast<const MetaCallHelper*
>(e)->
id(); };
666 for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) {
667 QPostEvent& cur = *it;
668 if (cur.receiver != receiver || cur.event == 0 || cur.event->type() !=
event->type()) {
671 const QMetaCallEvent* cur_mce =
static_cast<QMetaCallEvent*
>(cur.event);
672 if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() ||
673 methodId(cur_mce) != methodId(mce)) {
682 struct EventHelper :
private QEvent
684 static void clearPostedFlag(QEvent* ev)
686 (&
static_cast<EventHelper*
>(ev)->t)[1] &= ~0x8001;
690 EventHelper::clearPostedFlag(cur.event);
703 if (e->type() == QEvent::Quit) {
704 if (!_mainRootWindow) {
705 return QGuiApplication::event(e);
711 const bool forceClose = _mainRootWindow->property(
"_forceClose").toBool();
712 qCDebug(QGCApplicationLog) <<
"Quit event" << forceClose;
719 _mainRootWindow->close();
725 return QGuiApplication::event(e);
730 return dynamic_cast<QGCImageProvider*
>(_qmlAppEngine->imageProvider(_qgcImageProviderId));
735 qCDebug(QGCApplicationLog) <<
"Exit";
737 if (_videoManagerInitialized) {
743 if (_runningUnitTests || _simpleBootTest) {
744 const QSettings settings;
745 const QString settingsFile = settings.fileName();
746 if (QFile::exists(settingsFile)) {
747 if (QFile::remove(settingsFile)) {
748 qCDebug(QGCApplicationLog) <<
"Removed test run settings file:" << settingsFile;
750 qCWarning(QGCApplicationLog) <<
"Failed to remove test run settings file:" << settingsFile;
756 settingsAppDir.cdUp();
757 if (settingsAppDir.exists()) {
758 if (settingsAppDir.removeRecursively()) {
759 qCDebug(QGCApplicationLog) <<
"Removed test run settings directory:" << settingsAppDir.absolutePath();
761 qCWarning(QGCApplicationLog)
762 <<
"Failed to remove test run settings directory:" << settingsAppDir.absolutePath();
767 if (appDir.exists()) {
768 if (appDir.removeRecursively()) {
769 qCDebug(QGCApplicationLog) <<
"Removed test run app data directory:" << appDir.absolutePath();
771 qCWarning(QGCApplicationLog)
772 <<
"Failed to remove test run app data directory:" << appDir.absolutePath();
778 delete _qmlAppEngine;
Unified file download utility with decompression, verification, and QML support.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static constexpr const char * clearSettingsNextBootKey
static AudioOutput * instance()
void init(Fact *volumeFact, Fact *mutedFact)
Initialize the Singleton.
Image provider that rasterizes an SVG (or any QImage-loadable Qt resource)
static constexpr const char * ProviderId
static FollowMe * instance()
static JoystickManager * instance()
void loadLinkConfigurationList()
void startAutoConnectedLinks()
static LinkManager * instance()
static LogManager * instance()
void checkForLostLogFiles()
static MAVLinkProtocol * instance()
static MultiVehicleManager * instance()
static NTRIPManager * instance()
static QDir parameterCacheDir()
The main application and management class.
void qmlAttemptWindowClose()
void reportMissingParameter(int componentId, const QString &name)
void showRebootAppMessage(const QString &message, const QString &title=QString())
void init()
Perform initialize which is common to both normal application running and unit tests.
static QString cachedParameterMetaDataFile()
QQuickWindow * mainRootWindow()
void removeCompressedSignal(const QMetaMethod &method)
void languageChanged(const QLocale &locale)
static QString cachedAirframeMetaDataFile()
void addCompressedSignal(const QMetaMethod &method)
Registers the signal such that only the last duplicate signal added is left in the queue.
void showCriticalVehicleMessage(const QString &message)
Show non-modal vehicle message to the user.
bool runningUnitTests() const
QGCImageProvider * qgcImageProvider()
void showAppMessage(const QString &message, const QString &title=QString())
Show modal application message to the user.
bool event(QEvent *e) final
virtual QQmlApplicationEngine * createQmlApplicationEngine(QObject *parent)
virtual void createRootWindow(QQmlApplicationEngine *qmlEngine)
Allows the plugin to override the creation of the root (native) window.
virtual QString stableVersionCheckFileUrl() const
static QGCCorePlugin * instance()
File download with progress, decompression, and hash verification.
void finished(bool success, const QString &localPath, const QString &errorMessage)
bool start(const QString &remoteUrl)
QString errorString() const
This is used to expose images from ImageProtocolHandler.
Q_INVOKABLE void setCategoryEnabled(const QString &fullCategoryName, bool enable)
static QGCLoggingCategoryManager * instance()
void installFilter(const QString &commandLineLoggingOptions=QString())
static QGCPositionManager * instance()
static SettingsManager * instance()
MavlinkSettings * mavlinkSettings() const
void startVideoBackendInit()
bool waitForVideoBackendReady(std::chrono::milliseconds timeout=std::chrono::minutes(1))
static VideoManager * instance()
void init(QQuickWindow *mainWindow)
void configureMainWindow(QQuickWindow *window)
QTranslator * translator()
Translator used by openInternalQGCJsonFile for localized strings.
void initializeProxySupport()