3#include <QtCore/QEvent>
5#include <QtCore/QMetaMethod>
6#include <QtCore/QMetaObject>
7#include <QtCore/private/qthread_p.h>
8#include <QtGui/QFontDatabase>
10#include <QtQml/QQmlApplicationEngine>
11#include <QtQml/QQmlContext>
12#include <QtQuick/QQuickImageProvider>
13#include <QtQuick/QQuickWindow>
14#include <QtQuickControls2/QQuickStyle>
15#include <QtSvg/QSvgRenderer>
41#include "qgc_version.h"
43#ifndef QGC_NO_SERIAL_LINK
51 : QGuiApplication(argc, argv),
52 _runningUnitTests(cli.runningUnitTests),
53 _simpleBootTest(cli.simpleBootTest),
54 _fakeMobile(cli.fakeMobile),
55 _logOutput(cli.logOutput),
56 _systemId(cli.systemId.value_or(0))
58 _msecsElapsedTime.start();
63 bool fClearSettingsOptions = cli.clearSettingsOptions;
64 const bool fClearCache = cli.clearCache;
65 const QString loggingOptions = cli.loggingOptions.value_or(QString(
""));
68 _missingParamsDelayedDisplayTimer.setSingleShot(
true);
69 _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout);
70 (void) connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout,
this, &QGCApplication::_missingParamsDisplay);
73 QString applicationName;
74 if (_runningUnitTests || _simpleBootTest) {
78 if (!cli.unitTests.isEmpty()) {
79 applicationName = QStringLiteral(
"%1_unittest_%2").arg(QGC_APP_NAME, cli.unitTests.first());
82 QStringLiteral(
"%1_unittest_%2").arg(QGC_APP_NAME).arg(QCoreApplication::applicationPid());
88 applicationName = QStringLiteral(
"%1 Daily").arg(QGC_APP_NAME);
90 applicationName = QGC_APP_NAME;
93 setApplicationName(applicationName);
94 setDesktopFileName(QGC_PACKAGE_NAME);
95 setOrganizationName(QGC_ORG_NAME);
96 setOrganizationDomain(QGC_ORG_DOMAIN);
97 setApplicationVersion(QString(QGC_APP_VERSION_STR));
100 QSettings::setDefaultFormat(QSettings::IniFormat);
102 qCDebug(QGCApplicationLog) <<
"Settings location" << settings.fileName()
103 <<
"Is writable?:" << settings.isWritable();
105 if (!settings.isWritable()) {
106 qCWarning(QGCApplicationLog) <<
"Settings location is not writable";
112 if (_runningUnitTests || _simpleBootTest) {
114 fClearSettingsOptions =
true;
117 if (fClearSettingsOptions) {
123 paramDir.removeRecursively();
124 paramDir.mkpath(paramDir.absolutePath());
128 if (settings.contains(_settingsVersionKey)) {
129 if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) {
131 _settingsUpgraded =
true;
135 settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
139 dir.removeRecursively();
140 QFile parameter(cachedParameterMetaDataFile());
142 QFile airframe(cachedAirframeMetaDataFile());
146 const QString metaDataCachePath =
147 QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral(
"/ParameterMetaData");
148 QDir(metaDataCachePath).removeRecursively();
155 if (_runningUnitTests) {
165 QSvgRenderer::setDefaultOptions(QtSvg::Tiny12FeaturesOnly);
170 _locale = QLocale::system();
171 qCDebug(QGCApplicationLog) <<
"System reported locale:" << _locale <<
"; Name" << _locale.name()
172 <<
"; Preferred (used in maps): "
173 << (QLocale::system().uiLanguages().length() > 0 ? QLocale::system().uiLanguages()[0]
176 QLocale::Language possibleLocale = AppSettings::_qLocaleLanguageEarlyAccess();
177 if (possibleLocale != QLocale::AnyLanguage) {
178 _locale = QLocale(possibleLocale);
181 if (_locale == QLocale::Korean) {
182 qCDebug(QGCApplicationLog) <<
"Loading Korean fonts" << _locale.name();
183 if (QFontDatabase::addApplicationFont(
":/fonts/NanumGothic-Regular") < 0) {
184 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/NanumGothic-Regular font";
186 if (QFontDatabase::addApplicationFont(
":/fonts/NanumGothic-Bold") < 0) {
187 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/NanumGothic-Bold font";
190 qCDebug(QGCApplicationLog) <<
"Loading localizations for" << _locale.name();
192 removeTranslator(&_qgcTranslatorSourceCode);
193 removeTranslator(&_qgcTranslatorQtLibs);
194 if (_locale.name() !=
"en_US") {
195 QLocale::setDefault(_locale);
196 if (_qgcTranslatorQtLibs.load(
"qt_" + _locale.name(), QLibraryInfo::path(QLibraryInfo::TranslationsPath))) {
197 installTranslator(&_qgcTranslatorQtLibs);
199 qCWarning(QGCApplicationLog) <<
"Qt lib localization for" << _locale.name() <<
"is not present";
201 if (_qgcTranslatorSourceCode.load(_locale, QLatin1String(
"qgc_source_"),
"",
":/i18n")) {
202 installTranslator(&_qgcTranslatorSourceCode);
204 qCWarning(QGCApplicationLog) <<
"Error loading source localization for" << _locale.name();
209 qCWarning(QGCApplicationLog) <<
"Error loading json localization for" << _locale.name();
214 _qmlAppEngine->retranslate();
226 qCDebug(QGCApplicationLog) <<
"Setting MAVLink System ID to:" << _systemId;
234 if (QFontDatabase::addApplicationFont(
":/fonts/opensans") < 0) {
235 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/opensans font";
238 if (QFontDatabase::addApplicationFont(
":/fonts/opensans-demibold") < 0) {
239 qCWarning(QGCApplicationLog) <<
"Could not load /fonts/opensans-demibold font";
242 if (_simpleBootTest) {
245 const bool videoInitialized = _initVideo();
246 const bool qmlRootLoaded = _initQmlRootWindow();
247 _bootTestPassed = videoInitialized && qmlRootLoaded;
248 }
else if (!_runningUnitTests) {
249 _initForNormalAppBoot();
253bool QGCApplication::_initVideo()
255#ifdef QGC_GST_STREAMING
256 qCDebug(QGCApplicationLog) <<
"Using default graphics API for appsink → VideoOutput video path";
263 _videoManagerInitialized =
true;
264 return initSucceeded;
267bool QGCApplication::_initQmlRootWindow()
269 QQuickStyle::setStyle(
"Basic");
274 QObject::connect(_qmlAppEngine, &QQmlApplicationEngine::objectCreationFailed,
this, QCoreApplication::quit,
275 Qt::QueuedConnection);
279 _qmlAppEngine->addImageProvider(_qgcImageProviderId,
new QGCImageProvider());
291void QGCApplication::_initForNormalAppBoot()
295 (void) _initQmlRootWindow();
306 QUrl windowIcon = QUrl(
"qrc:/res/qgroundcontrol.ico");
307 windowIcon = _qmlAppEngine->interceptUrl(windowIcon, QQmlAbstractUrlInterceptor::UrlString);
309 setWindowIcon(QIcon(
":" + windowIcon.path()));
313 _showErrorsInToolbar =
true;
317#ifndef QGC_NO_SERIAL_LINK
318 if (!_runningUnitTests) {
320 QFile permFile(
"/etc/group");
321 if (permFile.open(QIODevice::ReadOnly)) {
322 while (!permFile.atEnd()) {
323 const QString
line = permFile.readLine();
324 if (
line.contains(
"dialout") && !
line.contains(getenv(
"USER"))) {
327 tr(
"The current user does not have the correct permissions to access serial devices. "
328 "You should also remove modemmanager since it also interferes.<br/><br/>"
329 "If you are using Ubuntu, execute the following commands to fix these issues:<br/>"
330 "<pre>sudo usermod -a -G dialout $USER<br/>"
331 "sudo apt-get remove modemmanager</pre>"));
352 if (_settingsUpgraded) {
353 showAppMessage(tr(
"The format for %1 saved settings has been modified. "
354 "Your saved settings have been reset to defaults.")
355 .arg(applicationName()));
361#ifndef QGC_DAILY_BUILD
368 const QPair<int, QString> missingParam(componentId, name);
370 if (!_missingParams.contains(missingParam)) {
371 _missingParams.append(missingParam);
373 _missingParamsDelayedDisplayTimer.start();
376void QGCApplication::_missingParamsDisplay()
378 if (_missingParams.isEmpty()) {
383 for (QPair<int, QString>& missingParam : _missingParams) {
384 const QString param = QStringLiteral(
"%1:%2").arg(missingParam.first).arg(missingParam.second);
385 if (params.isEmpty()) {
388 params += QStringLiteral(
", %1").arg(param);
391 _missingParams.clear();
393 showAppMessage(tr(
"Parameters are missing from firmware. You may be running a version of firmware which is not "
394 "fully supported or your firmware has a bug in it. Missing params: %1")
398QObject* QGCApplication::_rootQmlObject()
400 if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) {
401 return _qmlAppEngine->rootObjects()[0];
410 if (message.startsWith(QStringLiteral(
"PreArm")) ||
411 message.startsWith(QStringLiteral(
"preflight"), Qt::CaseInsensitive)) {
415 QObject*
const rootQmlObject = _rootQmlObject();
416 if (rootQmlObject && _showErrorsInToolbar) {
418 QVariant varMessage = QVariant::fromValue(message);
419 QMetaObject::invokeMethod(rootQmlObject,
"showCriticalVehicleMessage", Q_RETURN_ARG(QVariant, varReturn),
420 Q_ARG(QVariant, varMessage));
423 qCDebug(QGCApplicationLog) <<
"QGCApplication::showCriticalVehicleMessage unittest" << message;
425 qCWarning(QGCApplicationLog) <<
"Internal error";
431 const QString dialogTitle = title.isEmpty() ? applicationName() : title;
436 qCDebug(QGCAppMessageLog) <<
"showAppMessage:" << dialogTitle <<
"-" << message;
446 QObject*
const rootQmlObject = _rootQmlObject();
449 QVariant varMessage = QVariant::fromValue(message);
450 QMetaObject::invokeMethod(rootQmlObject,
"_showMessageDialog", Q_RETURN_ARG(QVariant, varReturn),
451 Q_ARG(QVariant, dialogTitle), Q_ARG(QVariant, varMessage));
454 _delayedAppMessages.append(QPair<QString, QString>(dialogTitle, message));
455 QTimer::singleShot(200,
this, &QGCApplication::_showDelayedAppMessages);
459bool QGCApplication::_rebootMessageDebounced()
461 const QTime currentTime = QTime::currentTime();
462 const QTime previousTime = _lastRebootMessageTime;
463 _lastRebootMessageTime = currentTime;
465 return previousTime.isValid() && (previousTime.msecsTo(currentTime) < (60 * 1000 * 2));
470 if (_rebootMessageDebounced()) {
477void QGCApplication::_showDelayedAppMessages()
479 if (_rootQmlObject()) {
480 for (
const QPair<QString, QString>& appMsg : _delayedAppMessages) {
483 _delayedAppMessages.clear();
485 QTimer::singleShot(200,
this, &QGCApplication::_showDelayedAppMessages);
491 if (!_mainRootWindow) {
492 _mainRootWindow = qobject_cast<QQuickWindow*>(_rootQmlObject());
495 return _mainRootWindow;
500 if (_rootQmlObject()) {
501 QMetaObject::invokeMethod(_rootQmlObject(),
"attemptWindowClose");
508 const QDir parameterDir = QFileInfo(settings.fileName()).dir();
509 return parameterDir.filePath(QStringLiteral(
"ParameterFactMetaData.json"));
515 const QDir airframeDir = QFileInfo(settings.fileName()).dir();
516 return airframeDir.filePath(QStringLiteral(
"PX4AirframeFactMetaData.xml"));
519int QGCApplication::CompressedSignalList::_signalIndex(
const QMetaMethod& method)
521 if (method.methodType() != QMetaMethod::Signal) {
522 qCWarning(QGCApplicationLog) <<
"Internal error:" << Q_FUNC_INFO <<
"not a signal" << method.methodType();
527 const QMetaObject* metaObject = method.enclosingMetaObject();
528 for (
int i = 0; i <= method.methodIndex(); i++) {
529 if (metaObject->method(i).methodType() != QMetaMethod::Signal) {
538void QGCApplication::CompressedSignalList::add(
const QMetaMethod& method)
540 const QMetaObject* metaObject = method.enclosingMetaObject();
541 const int signalIndex = _signalIndex(method);
543 if (signalIndex != -1 && !contains(metaObject, signalIndex)) {
544 _signalMap[method.enclosingMetaObject()].insert(signalIndex);
548void QGCApplication::CompressedSignalList::remove(
const QMetaMethod& method)
550 const int signalIndex = _signalIndex(method);
551 const QMetaObject*
const metaObject = method.enclosingMetaObject();
553 if (signalIndex != -1 && _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex)) {
554 _signalMap[metaObject].remove(signalIndex);
555 if (_signalMap[metaObject].count() == 0) {
556 _signalMap.remove(metaObject);
561bool QGCApplication::CompressedSignalList::contains(
const QMetaObject* metaObject,
int signalIndex)
563 return _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex);
568 _compressedSignals.add(method);
573 _compressedSignals.remove(method);
578QT_WARNING_DISABLE_DEPRECATED
579bool QGCApplication::compressEvent(QEvent* event, QObject* receiver, QPostEventList* postedEvents)
581 if (
event->type() != QEvent::MetaCall) {
582 return QGuiApplication::compressEvent(
event, receiver, postedEvents);
585 const QMetaCallEvent* mce =
static_cast<QMetaCallEvent*
>(
event);
586 if (!mce->sender() || !_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) {
587 return QGuiApplication::compressEvent(
event, receiver, postedEvents);
591 struct MetaCallHelper :
public QMetaCallEvent {
592 int id()
const {
return d.method_offset_ + d.method_relative_; }
594 const auto methodId = [](
const QMetaCallEvent *e) {
return static_cast<const MetaCallHelper*
>(e)->
id(); };
596 for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) {
597 QPostEvent& cur = *it;
598 if (cur.receiver != receiver || cur.event == 0 || cur.event->type() !=
event->type()) {
601 const QMetaCallEvent* cur_mce =
static_cast<QMetaCallEvent*
>(cur.event);
602 if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() ||
603 methodId(cur_mce) != methodId(mce)) {
612 struct EventHelper :
private QEvent
614 static void clearPostedFlag(QEvent* ev)
616 (&
static_cast<EventHelper*
>(ev)->t)[1] &= ~0x8001;
620 EventHelper::clearPostedFlag(cur.event);
633 if (e->type() == QEvent::Quit) {
634 if (!_mainRootWindow) {
635 return QGuiApplication::event(e);
641 const bool forceClose = _mainRootWindow->property(
"_forceClose").toBool();
642 qCDebug(QGCApplicationLog) <<
"Quit event" << forceClose;
649 _mainRootWindow->close();
655 return QGuiApplication::event(e);
660 return dynamic_cast<QGCImageProvider*
>(_qmlAppEngine->imageProvider(_qgcImageProviderId));
666 qCDebug(QGCApplicationLog) <<
"Exit";
668 if (_videoManagerInitialized) {
676 _qmlAppEngine =
nullptr;
681 if (_runningUnitTests || _simpleBootTest) {
682 const QSettings settings;
683 const QString settingsFile = settings.fileName();
684 if (QFile::exists(settingsFile)) {
685 if (QFile::remove(settingsFile)) {
686 qCDebug(QGCApplicationLog) <<
"Removed test run settings file:" << settingsFile;
688 qCWarning(QGCApplicationLog) <<
"Failed to remove test run settings file:" << settingsFile;
694 settingsAppDir.cdUp();
695 if (settingsAppDir.exists()) {
696 if (settingsAppDir.removeRecursively()) {
697 qCDebug(QGCApplicationLog) <<
"Removed test run settings directory:" << settingsAppDir.absolutePath();
699 qCWarning(QGCApplicationLog)
700 <<
"Failed to remove test run settings directory:" << settingsAppDir.absolutePath();
705 if (appDir.exists()) {
706 if (appDir.removeRecursively()) {
707 qCDebug(QGCApplicationLog) <<
"Removed test run app data directory:" << appDir.absolutePath();
709 qCWarning(QGCApplicationLog)
710 <<
"Failed to remove test run app data directory:" << appDir.absolutePath();
716 delete _qmlAppEngine;
#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()
void shutdown()
Stops the GPS services, then the position manager.
static GPSManager * instance()
static JoystickManager * instance()
void loadLinkConfigurationList()
void startAutoConnectedLinks()
static LinkManager * instance()
static LogManager * instance()
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 void linkConfigurationsLoaded(LinkManager *linkManager)
Called at boot after saved link configurations are loaded and before auto-connect links are started.
virtual QQmlApplicationEngine * createQmlApplicationEngine(QObject *parent)
virtual void createRootWindow(QQmlApplicationEngine *qmlEngine)
Allows the plugin to override the creation of the root (native) window.
virtual void destroyQmlApplicationEngine(QQmlApplicationEngine *qmlEngine)
static QGCCorePlugin * instance()
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 QGCVersionCheck * instance()
void start()
Starts the asynchronous download of the stable version file.
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)
QByteArray line(std::string_view command)
command as one CR/LF-terminated line.
void configureMainWindow(QQuickWindow *window)
QTranslator * translator()
Translator used by openInternalQGCJsonFile for localized strings.
void initializeProxySupport()