3#include "qgc_version.h"
5#include <QtCore/QEvent>
7#include <QtCore/QMetaMethod>
8#include <QtCore/QMetaObject>
9#include <QtCore/QRegularExpression>
10#include <QtGui/QFontDatabase>
13#include <QtQml/QQmlApplicationEngine>
14#include <QtQml/QQmlContext>
15#include <QtQuick/QQuickImageProvider>
16#include <QtQuick/QQuickWindow>
17#include <QtQuickControls2/QQuickStyle>
18#include <QtSvg/QSvgRenderer>
20#include <QtCore/private/qthread_p.h>
47#ifndef QGC_NO_SERIAL_LINK
54 : QGuiApplication(argc, argv)
55 , _runningUnitTests(cli.runningUnitTests)
56 , _simpleBootTest(cli.simpleBootTest)
57 , _fakeMobile(cli.fakeMobile)
58 , _logOutput(cli.logOutput)
59 , _systemId(cli.systemId.value_or(0))
61 _msecsElapsedTime.start();
66 bool fClearSettingsOptions = cli.clearSettingsOptions;
67 const bool fClearCache = cli.clearCache;
68 const QString loggingOptions = cli.loggingOptions.value_or(QString(
""));
71 _missingParamsDelayedDisplayTimer.setSingleShot(
true);
72 _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout);
73 (void) connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout,
this, &QGCApplication::_missingParamsDisplay);
76 QString applicationName;
77 if (_runningUnitTests || _simpleBootTest) {
81 if (!cli.unitTests.isEmpty()) {
82 applicationName = QStringLiteral(
"%1_unittest_%2").arg(QGC_APP_NAME, cli.unitTests.first());
84 applicationName = 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() <<
"Is writable?:" << settings.isWritable();
106 if (!settings.isWritable()) {
107 qCWarning(QGCApplicationLog) <<
"Setings location is not writable";
113 if (_runningUnitTests || _simpleBootTest) {
115 fClearSettingsOptions =
true;
118 if (fClearSettingsOptions) {
124 paramDir.removeRecursively();
125 paramDir.mkpath(paramDir.absolutePath());
129 if (settings.contains(_settingsVersionKey)) {
130 if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) {
132 _settingsUpgraded =
true;
136 settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
140 dir.removeRecursively();
141 QFile parameter(cachedParameterMetaDataFile());
143 QFile airframe(cachedAirframeMetaDataFile());
147 const QString metaDataCachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
148 + QStringLiteral(
"/ParameterMetaData");
149 QDir(metaDataCachePath).removeRecursively();
160 QSvgRenderer::setDefaultOptions(QtSvg::Tiny12FeaturesOnly);
162#ifndef QGC_DAILY_BUILD
163 _checkForNewVersion();
169 _locale = QLocale::system();
170 qCDebug(QGCApplicationLog) <<
"System reported locale:" << _locale <<
"; Name" << _locale.name() <<
"; Preffered (used in maps): " << (QLocale::system().uiLanguages().length() > 0 ? QLocale::system().uiLanguages()[0] :
"None");
172 QLocale::Language possibleLocale = AppSettings::_qLocaleLanguageEarlyAccess();
173 if (possibleLocale != QLocale::AnyLanguage) {
174 _locale = QLocale(possibleLocale);
177 if (_locale == QLocale::Korean) {
178 qCDebug(QGCApplicationLog) <<
"Loading Korean fonts" << _locale.name();
179 if(QFontDatabase::addApplicationFont(
":/fonts/NanumGothic-Regular") < 0) {
180 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/NanumGothic-Regular font";
182 if(QFontDatabase::addApplicationFont(
":/fonts/NanumGothic-Bold") < 0) {
183 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/NanumGothic-Bold font";
186 qCDebug(QGCApplicationLog) <<
"Loading localizations for" << _locale.name();
188 removeTranslator(&_qgcTranslatorSourceCode);
189 removeTranslator(&_qgcTranslatorQtLibs);
190 if (_locale.name() !=
"en_US") {
191 QLocale::setDefault(_locale);
192 if (_qgcTranslatorQtLibs.load(
"qt_" + _locale.name(), QLibraryInfo::path(QLibraryInfo::TranslationsPath))) {
193 installTranslator(&_qgcTranslatorQtLibs);
195 qCWarning(QGCApplicationLog) <<
"Qt lib localization for" << _locale.name() <<
"is not present";
197 if (_qgcTranslatorSourceCode.load(_locale, QLatin1String(
"qgc_source_"),
"",
":/i18n")) {
198 installTranslator(&_qgcTranslatorSourceCode);
200 qCWarning(QGCApplicationLog) <<
"Error loading source localization for" << _locale.name();
205 qCWarning(QGCApplicationLog) <<
"Error loading json localization for" << _locale.name();
210 _qmlAppEngine->retranslate();
225 qCDebug(QGCApplicationLog) <<
"Setting MAVLink System ID to:" << _systemId;
233 logMgr->setLogDirectory(appSettings->logSavePath());
235 logMgr->setLogDirectory(appSettings->logSavePath());
243 auto applySetting = [appSettings, sink]() {
244 sink->
setHost(appSettings->remoteLoggingHost()->rawValue().toString());
245 sink->setPort(
static_cast<quint16
>(appSettings->remoteLoggingPort()->rawValue().toUInt()));
247 appSettings->remoteLoggingProtocol()->rawValue().toInt()));
248 sink->setVehicleId(appSettings->remoteLoggingVehicleId()->rawValue().toString());
249 sink->setTlsEnabled(appSettings->remoteLoggingTlsEnabled()->rawValue().toBool());
250 sink->setTlsVerifyPeer(appSettings->remoteLoggingTlsVerifyPeer()->rawValue().toBool());
251 sink->setCompressionEnabled(appSettings->remoteLoggingCompressionEnabled()->rawValue().toBool());
252 sink->setCompressionLevel(appSettings->remoteLoggingCompressionLevel()->rawValue().toInt());
253 sink->setEnabled(appSettings->remoteLoggingEnabled()->rawValue().toBool());
256 appSettings->remoteLoggingEnabled(), appSettings->remoteLoggingHost(),
257 appSettings->remoteLoggingPort(), appSettings->remoteLoggingProtocol(),
258 appSettings->remoteLoggingVehicleId(), appSettings->remoteLoggingTlsEnabled(),
259 appSettings->remoteLoggingTlsVerifyPeer(),
260 appSettings->remoteLoggingCompressionEnabled(),
261 appSettings->remoteLoggingCompressionLevel(),
269 if (QFontDatabase::addApplicationFont(
":/fonts/opensans") < 0) {
270 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/opensans font";
273 if (QFontDatabase::addApplicationFont(
":/fonts/opensans-demibold") < 0) {
274 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/opensans-demibold font";
277 if (_simpleBootTest) {
280 _bootTestPassed = _initVideo();
281 }
else if (!_runningUnitTests) {
282 _initForNormalAppBoot();
286bool QGCApplication::_initVideo()
288#ifdef QGC_GST_STREAMING
289 qCDebug(QGCApplicationLog) <<
"Using default graphics API for appsink → VideoOutput video path";
296 _videoManagerInitialized =
true;
297 return initSucceeded;
300void QGCApplication::_initForNormalAppBoot()
304 QQuickStyle::setStyle(
"Basic");
309 QObject::connect(_qmlAppEngine, &QQmlApplicationEngine::objectCreationFailed,
this, QCoreApplication::quit, Qt::QueuedConnection);
312 _qmlAppEngine->addImageProvider(_qgcImageProviderId,
new QGCImageProvider());
325 QUrl windowIcon = QUrl(
"qrc:/res/qgroundcontrol.ico");
326 windowIcon = _qmlAppEngine->interceptUrl(windowIcon, QQmlAbstractUrlInterceptor::UrlString);
328 setWindowIcon(QIcon(
":" + windowIcon.path()));
332 _showErrorsInToolbar =
true;
336 #ifndef QGC_NO_SERIAL_LINK
337 if (!_runningUnitTests) {
339 QFile permFile(
"/etc/group");
340 if(permFile.open(QIODevice::ReadOnly)) {
341 while(!permFile.atEnd()) {
342 const QString line = permFile.readLine();
343 if (line.contains(
"dialout") && !line.contains(getenv(
"USER"))) {
346 "The current user does not have the correct permissions to access serial devices. "
347 "You should also remove modemmanager since it also interferes.<br/><br/>"
348 "If you are using Ubuntu, execute the following commands to fix these issues:<br/>"
349 "<pre>sudo usermod -a -G dialout $USER<br/>"
350 "sudo apt-get remove modemmanager</pre>"));
370 if (_settingsUpgraded) {
371 showAppMessage(tr(
"The format for %1 saved settings has been modified. "
372 "Your saved settings have been reset to defaults.").arg(applicationName()));
381 const QPair<int, QString> missingParam(componentId, name);
383 if (!_missingParams.contains(missingParam)) {
384 _missingParams.append(missingParam);
386 _missingParamsDelayedDisplayTimer.start();
389void QGCApplication::_missingParamsDisplay()
391 if (_missingParams.isEmpty()) {
396 for (QPair<int, QString>& missingParam: _missingParams) {
397 const QString param = QStringLiteral(
"%1:%2").arg(missingParam.first).arg(missingParam.second);
398 if (params.isEmpty()) {
401 params += QStringLiteral(
", %1").arg(param);
405 _missingParams.clear();
407 showAppMessage(tr(
"Parameters are missing from firmware. You may be running a version of firmware which is not fully supported or your firmware has a bug in it. Missing params: %1").arg(params));
410QObject *QGCApplication::_rootQmlObject()
412 if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) {
413 return _qmlAppEngine->rootObjects()[0];
422 if (message.startsWith(QStringLiteral(
"PreArm")) || message.startsWith(QStringLiteral(
"preflight"), Qt::CaseInsensitive)) {
426 QObject *
const rootQmlObject = _rootQmlObject();
427 if (rootQmlObject && _showErrorsInToolbar) {
429 QVariant varMessage = QVariant::fromValue(message);
430 QMetaObject::invokeMethod(rootQmlObject,
"showCriticalVehicleMessage", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, varMessage));
433 qCDebug(QGCApplicationLog) <<
"QGCApplication::showCriticalVehicleMessage unittest" << message;
435 qCWarning(QGCApplicationLog) <<
"Internal error";
441 const QString dialogTitle = title.isEmpty() ? applicationName() : title;
443 QObject *
const rootQmlObject = _rootQmlObject();
446 QVariant varMessage = QVariant::fromValue(message);
447 QMetaObject::invokeMethod(rootQmlObject,
"_showMessageDialog", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, dialogTitle), Q_ARG(QVariant, varMessage));
451 qDebug() <<
"QGCApplication::showAppMessage unittest title:message" << dialogTitle << message;
454 _delayedAppMessages.append(QPair<QString, QString>(dialogTitle, message));
455 QTimer::singleShot(200,
this, &QGCApplication::_showDelayedAppMessages);
461 static QTime lastRebootMessage;
463 const QTime currentTime = QTime::currentTime();
464 const QTime previousTime = lastRebootMessage;
465 lastRebootMessage = currentTime;
467 if (previousTime.isValid() && (previousTime.msecsTo(currentTime) < (60 * 1000 * 2))) {
475void QGCApplication::_showDelayedAppMessages()
477 if (_rootQmlObject()) {
478 for (
const QPair<QString, QString>& appMsg: _delayedAppMessages) {
481 _delayedAppMessages.clear();
483 QTimer::singleShot(200,
this, &QGCApplication::_showDelayedAppMessages);
489 if (!_mainRootWindow) {
490 _mainRootWindow = qobject_cast<QQuickWindow*>(_rootQmlObject());
493 return _mainRootWindow;
498 if (_rootQmlObject()) {
499 QMetaObject::invokeMethod(_rootQmlObject(),
"showVehicleConfig");
505 if (_rootQmlObject()) {
506 QMetaObject::invokeMethod(_rootQmlObject(),
"attemptWindowClose");
510void QGCApplication::_checkForNewVersion()
512 if (_runningUnitTests) {
516 if (!_parseVersionText(applicationVersion(), _majorVersion, _minorVersion, _buildVersion)) {
521 if (!versionCheckFile.isEmpty()) {
524 if (!download->
start(versionCheckFile)) {
525 qCDebug(QGCApplicationLog) <<
"Download QGC stable version failed to start" << download->
errorString();
526 download->deleteLater();
531void QGCApplication::_qgcCurrentStableVersionDownloadComplete(
bool success,
const QString &localFile,
const QString &errorMsg)
534 QFile versionFile(localFile);
535 if (versionFile.open(QIODevice::ReadOnly)) {
536 QTextStream textStream(&versionFile);
537 const QString version = textStream.readLine();
539 qCDebug(QGCApplicationLog) << version;
541 int majorVersion, minorVersion, buildVersion;
542 if (_parseVersionText(version, majorVersion, minorVersion, buildVersion)) {
543 if (_majorVersion < majorVersion ||
544 ((_majorVersion == majorVersion) && (_minorVersion < minorVersion)) ||
545 ((_majorVersion == majorVersion) && (_minorVersion == minorVersion) && (_buildVersion < buildVersion))) {
546 showAppMessage(tr(
"There is a newer version of %1 available. You can download it from %2.").arg(applicationName()).arg(
QGCCorePlugin::instance()->stableDownloadLocation()), tr(
"New Version Available"));
550 }
else if (!errorMsg.isEmpty()) {
551 qCDebug(QGCApplicationLog) <<
"Download QGC stable version failed" << errorMsg;
554 sender()->deleteLater();
557bool QGCApplication::_parseVersionText(
const QString &versionString,
int &majorVersion,
int &minorVersion,
int &buildVersion)
559 static const QRegularExpression regExp(
"v(\\d+)\\.(\\d+)\\.(\\d+)");
560 const QRegularExpressionMatch match = regExp.match(versionString);
561 if (match.hasMatch() && match.lastCapturedIndex() == 3) {
562 majorVersion = match.captured(1).toInt();
563 minorVersion = match.captured(2).toInt();
564 buildVersion = match.captured(3).toInt();
574 const QDir parameterDir = QFileInfo(settings.fileName()).dir();
575 return parameterDir.filePath(QStringLiteral(
"ParameterFactMetaData.json"));
581 const QDir airframeDir = QFileInfo(settings.fileName()).dir();
582 return airframeDir.filePath(QStringLiteral(
"PX4AirframeFactMetaData.xml"));
585int QGCApplication::CompressedSignalList::_signalIndex(
const QMetaMethod &method)
587 if (method.methodType() != QMetaMethod::Signal) {
588 qCWarning(QGCApplicationLog) <<
"Internal error:" << Q_FUNC_INFO <<
"not a signal" << method.methodType();
593 const QMetaObject *metaObject = method.enclosingMetaObject();
594 for (
int i=0; i<=method.methodIndex(); i++) {
595 if (metaObject->method(i).methodType() != QMetaMethod::Signal) {
604void QGCApplication::CompressedSignalList::add(
const QMetaMethod &method)
606 const QMetaObject *metaObject = method.enclosingMetaObject();
607 const int signalIndex = _signalIndex(method);
609 if (signalIndex != -1 && !contains(metaObject, signalIndex)) {
610 _signalMap[method.enclosingMetaObject()].insert(signalIndex);
614void QGCApplication::CompressedSignalList::remove(
const QMetaMethod &method)
616 const int signalIndex = _signalIndex(method);
617 const QMetaObject *
const metaObject = method.enclosingMetaObject();
619 if (signalIndex != -1 && _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex)) {
620 _signalMap[metaObject].remove(signalIndex);
621 if (_signalMap[metaObject].count() == 0) {
622 _signalMap.remove(metaObject);
627bool QGCApplication::CompressedSignalList::contains(
const QMetaObject *metaObject,
int signalIndex)
629 return _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex);
634 _compressedSignals.add(method);
639 _compressedSignals.remove(method);
643QT_WARNING_DISABLE_DEPRECATED
644bool QGCApplication::compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents)
646 if (
event->type() != QEvent::MetaCall) {
647 return QGuiApplication::compressEvent(
event, receiver, postedEvents);
650 const QMetaCallEvent *mce =
static_cast<QMetaCallEvent*
>(
event);
651 if (!mce->sender() || !_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) {
652 return QGuiApplication::compressEvent(
event, receiver, postedEvents);
655 for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) {
656 QPostEvent &cur = *it;
657 if (cur.receiver != receiver || cur.event == 0 || cur.event->type() !=
event->type()) {
660 const QMetaCallEvent *cur_mce =
static_cast<QMetaCallEvent*
>(cur.event);
661 if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() || cur_mce->id() != mce->id()) {
669 struct EventHelper :
private QEvent {
670 static void clearPostedFlag(QEvent * ev) {
671 (&
static_cast<EventHelper*
>(ev)->t)[1] &= ~0x8001;
674 EventHelper::clearPostedFlag(cur.event);
686 if (e->type() == QEvent::Quit) {
687 if (!_mainRootWindow) {
688 return QGuiApplication::event(e);
693 const bool forceClose = _mainRootWindow->property(
"_forceClose").toBool();
694 qCDebug(QGCApplicationLog) <<
"Quit event" << forceClose;
700 _mainRootWindow->close();
706 return QGuiApplication::event(e);
711 return dynamic_cast<QGCImageProvider*
>(_qmlAppEngine->imageProvider(_qgcImageProviderId));
716 qCDebug(QGCApplicationLog) <<
"Exit";
718 if (_videoManagerInitialized) {
724 if (_runningUnitTests || _simpleBootTest) {
725 const QSettings settings;
726 const QString settingsFile = settings.fileName();
727 if (QFile::exists(settingsFile)) {
728 if (QFile::remove(settingsFile)) {
729 qCDebug(QGCApplicationLog) <<
"Removed test run settings file:" << settingsFile;
731 qCWarning(QGCApplicationLog) <<
"Failed to remove test run settings file:" << settingsFile;
737 settingsAppDir.cdUp();
738 if (settingsAppDir.exists()) {
739 if (settingsAppDir.removeRecursively()) {
740 qCDebug(QGCApplicationLog) <<
"Removed test run settings directory:" << settingsAppDir.absolutePath();
742 qCWarning(QGCApplicationLog) <<
"Failed to remove test run settings directory:" << settingsAppDir.absolutePath();
747 if (appDir.exists()) {
748 if (appDir.removeRecursively()) {
749 qCDebug(QGCApplicationLog) <<
"Removed test run app data directory:" << appDir.absolutePath();
751 qCWarning(QGCApplicationLog) <<
"Failed to remove test run app data directory:" << appDir.absolutePath();
757 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
void rawValueChanged(const QVariant &value)
static FollowMe * instance()
static JoystickManager * instance()
void loadLinkConfigurationList()
void startAutoConnectedLinks()
static LinkManager * instance()
LogRemoteSink * remoteSink()
static LogManager * instance()
void setHost(const QString &host)
void checkForLostLogFiles()
static MAVLinkProtocol * instance()
static MultiVehicleManager * 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.
static QGCLoggingCategoryManager * instance()
void installFilter(const QString &commandLineLoggingOptions=QString())
static QGCPositionManager * instance()
static SettingsManager * instance()
AppSettings * appSettings() const
MavlinkSettings * mavlinkSettings() const
static VideoManager * instance()
void startGStreamerInit()
void init(QQuickWindow *mainWindow)
bool waitForGStreamerInit(int timeoutMs=60000)
QTranslator * translator()
Translator used by openInternalQGCJsonFile for localized strings.
void initializeProxySupport()