QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCApplication.cc
Go to the documentation of this file.
1#include "QGCApplication.h"
2
3#include <QtCore/QEvent>
4#include <QtCore/QFile>
5#include <QtCore/QMetaMethod>
6#include <QtCore/QMetaObject>
7#include <QtCore/private/qthread_p.h>
8#include <QtGui/QFontDatabase>
9#include <QtGui/QIcon>
10#include <QtQml/QQmlApplicationEngine>
11#include <QtQml/QQmlContext>
12#include <QtQuick/QQuickImageProvider>
13#include <QtQuick/QQuickWindow>
14#include <QtQuickControls2/QQuickStyle>
15#include <QtSvg/QSvgRenderer>
16
17#include "AppSettings.h"
18#include "AudioOutput.h"
20#include "FollowMe.h"
21#include "GPSManager.h"
22#include "GraphicsSetup.h"
23#include "JoystickManager.h"
24#include "JsonParsing.h"
25#include "LinkManager.h"
26#include "LogManager.h"
27#include "MAVLinkProtocol.h"
28#include "MavlinkSettings.h"
29#include "MultiVehicleManager.h"
30#include "ParameterManager.h"
32#include "QGCCorePlugin.h"
33#include "QGCImageProvider.h"
34#include "QGCLoggingCategory.h"
36#include "QGCNetworkHelper.h"
37#include "QGCVersionCheck.h"
38#include "SettingsManager.h"
39#include "Vehicle.h"
40#include "VideoManager.h"
41#include "qgc_version.h"
42
43#ifndef QGC_NO_SERIAL_LINK
44#include "SerialLink.h"
45#endif
46
47QGC_LOGGING_CATEGORY(QGCApplicationLog, "API.QGCApplication")
48QGC_LOGGING_CATEGORY(QGCAppMessageLog, "API.QGCApplication.AppMessage")
49
50QGCApplication::QGCApplication(int& argc, char* argv[], const QGCCommandLineParser::CommandLineParseResult& cli)
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))
57{
58 _msecsElapsedTime.start();
59
60 // Setup for network proxy support
62
63 bool fClearSettingsOptions = cli.clearSettingsOptions; // Clear stored settings
64 const bool fClearCache = cli.clearCache; // Clear parameter/airframe caches
65 const QString loggingOptions = cli.loggingOptions.value_or(QString(""));
66
67 // Set up timer for delayed missing fact display
68 _missingParamsDelayedDisplayTimer.setSingleShot(true);
69 _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout);
70 (void) connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, &QGCApplication::_missingParamsDisplay);
71
72 // Set application information
73 QString applicationName;
74 if (_runningUnitTests || _simpleBootTest) {
75 // We don't want unit tests to use the same QSettings space as the normal app. So we tweak the app
76 // name. Also we want to run unit tests with clean settings every time.
77 // Include test name or PID to prevent settings file conflicts when tests run in parallel
78 if (!cli.unitTests.isEmpty()) {
79 applicationName = QStringLiteral("%1_unittest_%2").arg(QGC_APP_NAME, cli.unitTests.first());
80 } else {
81 applicationName =
82 QStringLiteral("%1_unittest_%2").arg(QGC_APP_NAME).arg(QCoreApplication::applicationPid());
83 }
84 } else {
85#ifdef QGC_DAILY_BUILD
86 // This gives daily builds their own separate settings space. Allowing you to use daily and stable builds
87 // side by side without daily screwing up your stable settings.
88 applicationName = QStringLiteral("%1 Daily").arg(QGC_APP_NAME);
89#else
90 applicationName = QGC_APP_NAME;
91#endif
92 }
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));
98
99 // Set settings format
100 QSettings::setDefaultFormat(QSettings::IniFormat);
101 QSettings settings;
102 qCDebug(QGCApplicationLog) << "Settings location" << settings.fileName()
103 << "Is writable?:" << settings.isWritable();
104
105 if (!settings.isWritable()) {
106 qCWarning(QGCApplicationLog) << "Settings location is not writable";
107 }
108
109 // The setting will delete all settings on this boot
110 fClearSettingsOptions |= settings.value(AppSettings::clearSettingsNextBootKey, false).toBool();
111
112 if (_runningUnitTests || _simpleBootTest) {
113 // Unit tests run with clean settings
114 fClearSettingsOptions = true;
115 }
116
117 if (fClearSettingsOptions) {
118 // User requested settings to be cleared on command line
119 settings.clear();
120
121 // Clear parameter cache
123 paramDir.removeRecursively();
124 paramDir.mkpath(paramDir.absolutePath());
125 } else {
126 // Determine if upgrade message for settings version bump is required. Check and clear must happen before
127 // toolbox is started since that will write some settings.
128 if (settings.contains(_settingsVersionKey)) {
129 if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) {
130 settings.clear();
131 _settingsUpgraded = true;
132 }
133 }
134 }
135 settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
136
137 if (fClearCache) {
139 dir.removeRecursively();
140 QFile parameter(cachedParameterMetaDataFile());
141 parameter.remove();
142 QFile airframe(cachedAirframeMetaDataFile());
143 airframe.remove();
144
145 // Clear versioned parameter metadata cache
146 const QString metaDataCachePath =
147 QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/ParameterMetaData");
148 QDir(metaDataCachePath).removeRecursively();
149 }
150
151 // Set up our logging filters
154
155 if (_runningUnitTests) {
156 // Enable the AppMessage category so showAppMessage() debug logs are visible during unit tests.
157 QGCLoggingCategoryManager::instance()->setCategoryEnabled(QStringLiteral("API.QGCApplication.AppMessage"),
158 true);
159 }
160
161 // We need to set language as early as possible prior to loading on JSON files.
162 setLanguage();
163
164 // Force old SVG Tiny 1.2 behavior for compatibility
165 QSvgRenderer::setDefaultOptions(QtSvg::Tiny12FeaturesOnly);
166}
167
169{
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]
174 : "None");
175
176 QLocale::Language possibleLocale = AppSettings::_qLocaleLanguageEarlyAccess();
177 if (possibleLocale != QLocale::AnyLanguage) {
178 _locale = QLocale(possibleLocale);
179 }
180 //-- We have specific fonts for Korean
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";
185 }
186 if (QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Bold") < 0) {
187 qCWarning(QGCApplicationLog) << "Could not load /fonts/NanumGothic-Bold font";
188 }
189 }
190 qCDebug(QGCApplicationLog) << "Loading localizations for" << _locale.name();
191 removeTranslator(JsonParsing::translator());
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);
198 } else {
199 qCWarning(QGCApplicationLog) << "Qt lib localization for" << _locale.name() << "is not present";
200 }
201 if (_qgcTranslatorSourceCode.load(_locale, QLatin1String("qgc_source_"), "", ":/i18n")) {
202 installTranslator(&_qgcTranslatorSourceCode);
203 } else {
204 qCWarning(QGCApplicationLog) << "Error loading source localization for" << _locale.name();
205 }
206 if (JsonParsing::translator()->load(_locale, QLatin1String("qgc_json_"), "", ":/i18n")) {
207 installTranslator(JsonParsing::translator());
208 } else {
209 qCWarning(QGCApplicationLog) << "Error loading json localization for" << _locale.name();
210 }
211 }
212
213 if (_qmlAppEngine) {
214 _qmlAppEngine->retranslate();
215 }
216
217 emit languageChanged(_locale);
218}
219
221
223{
225 if (_systemId > 0) {
226 qCDebug(QGCApplicationLog) << "Setting MAVLink System ID to:" << _systemId;
227 SettingsManager::instance()->mavlinkSettings()->gcsMavlinkSystemID()->setRawValue(_systemId);
228 }
229
231
232 // Although this should really be in _initForNormalAppBoot putting it here allowws us to create unit tests which pop
233 // up more easily
234 if (QFontDatabase::addApplicationFont(":/fonts/opensans") < 0) {
235 qCWarning(QGCApplicationLog) << "Could not load /fonts/opensans font";
236 }
237
238 if (QFontDatabase::addApplicationFont(":/fonts/opensans-demibold") < 0) {
239 qCWarning(QGCApplicationLog) << "Could not load /fonts/opensans-demibold font";
240 }
241
242 if (_simpleBootTest) {
243 // Since GStream builds are so problematic we initialize video during the simple boot test
244 // to make sure it works and verfies plugin availability.
245 const bool videoInitialized = _initVideo();
246 const bool qmlRootLoaded = _initQmlRootWindow();
247 _bootTestPassed = videoInitialized && qmlRootLoaded;
248 } else if (!_runningUnitTests) {
249 _initForNormalAppBoot();
250 }
251}
252
253bool QGCApplication::_initVideo()
254{
255#ifdef QGC_GST_STREAMING
256 qCDebug(QGCApplicationLog) << "Using default graphics API for appsink → VideoOutput video path";
257#endif
258
259 QGCCorePlugin::instance(); // CorePlugin must be initialized before VideoManager for Video Cleanup
260 VideoManager* videoManager = VideoManager::instance();
261 videoManager->startVideoBackendInit();
262 const bool initSucceeded = !_simpleBootTest || videoManager->waitForVideoBackendReady();
263 _videoManagerInitialized = true;
264 return initSucceeded;
265}
266
267bool QGCApplication::_initQmlRootWindow()
268{
269 QQuickStyle::setStyle("Basic");
274 QObject::connect(_qmlAppEngine, &QQmlApplicationEngine::objectCreationFailed, this, QCoreApplication::quit,
275 Qt::QueuedConnection);
276
277 // Must register before createRootWindow — root QML references QGCColoredImage which resolves image://coloredsvg/...
278 // at load time.
279 _qmlAppEngine->addImageProvider(_qgcImageProviderId, new QGCImageProvider());
280 _qmlAppEngine->addImageProvider(QLatin1String(ColoredSvgImageProvider::ProviderId), new ColoredSvgImageProvider());
281
283
284 // The root QQuickWindow exists now (load() is synchronous) but its scene graph has not been
285 // initialized yet -- the only safe point to apply RHI graphics config / forced device.
287
288 return mainRootWindow() != nullptr;
289}
290
291void QGCApplication::_initForNormalAppBoot()
292{
293 (void) _initVideo();
294
295 (void) _initQmlRootWindow();
296
297 AudioOutput::instance()->init(SettingsManager::instance()->appSettings()->audioVolume(),
298 SettingsManager::instance()->appSettings()->audioMuted());
303
304 // Set the window icon now that custom plugin has a chance to override it
305#ifdef Q_OS_LINUX
306 QUrl windowIcon = QUrl("qrc:/res/qgroundcontrol.ico");
307 windowIcon = _qmlAppEngine->interceptUrl(windowIcon, QQmlAbstractUrlInterceptor::UrlString);
308 // The interceptor needs "qrc:/path" but QIcon expects ":/path"
309 setWindowIcon(QIcon(":" + windowIcon.path()));
310#endif
311
312 // Safe to show popup error messages now that main window is created
313 _showErrorsInToolbar = true;
314
315#ifdef Q_OS_LINUX
316#ifndef Q_OS_ANDROID
317#ifndef QGC_NO_SERIAL_LINK
318 if (!_runningUnitTests) {
319 // Determine if we have the correct permissions to access USB serial devices
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"))) {
325 permFile.close();
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>"));
332 break;
333 }
334 }
335 permFile.close();
336 }
337 }
338#endif
339#endif
340#endif
341
342 // Now that main window is up check for lost log files
344
345 // Load known link configurations
348
349 // Probe for joysticks
351
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()));
356 }
357
358 // Connect links with flag AutoconnectLink
360
361#ifndef QGC_DAILY_BUILD
363#endif
364}
365
366void QGCApplication::reportMissingParameter(int componentId, const QString& name)
367{
368 const QPair<int, QString> missingParam(componentId, name);
369
370 if (!_missingParams.contains(missingParam)) {
371 _missingParams.append(missingParam);
372 }
373 _missingParamsDelayedDisplayTimer.start();
374}
375
376void QGCApplication::_missingParamsDisplay()
377{
378 if (_missingParams.isEmpty()) {
379 return;
380 }
381
382 QString params;
383 for (QPair<int, QString>& missingParam : _missingParams) {
384 const QString param = QStringLiteral("%1:%2").arg(missingParam.first).arg(missingParam.second);
385 if (params.isEmpty()) {
386 params += param;
387 } else {
388 params += QStringLiteral(", %1").arg(param);
389 }
390 }
391 _missingParams.clear();
392
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")
395 .arg(params));
396}
397
398QObject* QGCApplication::_rootQmlObject()
399{
400 if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) {
401 return _qmlAppEngine->rootObjects()[0];
402 }
403
404 return nullptr;
405}
406
408{
409 // PreArm messages are handled by Vehicle and shown in Map
410 if (message.startsWith(QStringLiteral("PreArm")) ||
411 message.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive)) {
412 return;
413 }
414
415 QObject* const rootQmlObject = _rootQmlObject();
416 if (rootQmlObject && _showErrorsInToolbar) {
417 QVariant varReturn;
418 QVariant varMessage = QVariant::fromValue(message);
419 QMetaObject::invokeMethod(rootQmlObject, "showCriticalVehicleMessage", Q_RETURN_ARG(QVariant, varReturn),
420 Q_ARG(QVariant, varMessage));
421 } else if (runningUnitTests() || !_showErrorsInToolbar) {
422 // Unit tests can run without UI
423 qCDebug(QGCApplicationLog) << "QGCApplication::showCriticalVehicleMessage unittest" << message;
424 } else {
425 qCWarning(QGCApplicationLog) << "Internal error";
426 }
427}
428
429void QGCApplication::showAppMessage(const QString& message, const QString& title)
430{
431 const QString dialogTitle = title.isEmpty() ? applicationName() : title;
432
433 if (runningUnitTests()) {
434 // Logged under QGCAppMessageLog so tests can assert expected dialogs via
435 // expectAppMessage() without matching against the general QGCApplication category.
436 qCDebug(QGCAppMessageLog) << "showAppMessage:" << dialogTitle << "-" << message;
437 if (!_uiTestMode) {
438 // Headless test: there is no QML root to host the dialog and the delayed-message
439 // timer would retry forever, so the log is the only record.
440 return;
441 }
442 // UI tests fall through: the message dialog is a non-blocking QML popup, so show it
443 // for real and let the test handle it the same way a user would.
444 }
445
446 QObject* const rootQmlObject = _rootQmlObject();
447 if (rootQmlObject) {
448 QVariant varReturn;
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));
452 } else {
453 // UI isn't ready yet
454 _delayedAppMessages.append(QPair<QString, QString>(dialogTitle, message));
455 QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages);
456 }
457}
458
459bool QGCApplication::_rebootMessageDebounced()
460{
461 const QTime currentTime = QTime::currentTime();
462 const QTime previousTime = _lastRebootMessageTime;
463 _lastRebootMessageTime = currentTime;
464
465 return previousTime.isValid() && (previousTime.msecsTo(currentTime) < (60 * 1000 * 2));
466}
467
468void QGCApplication::showRebootAppMessage(const QString& message, const QString& title)
469{
470 if (_rebootMessageDebounced()) {
471 return;
472 }
473
474 showAppMessage(message, title);
475}
476
477void QGCApplication::_showDelayedAppMessages()
478{
479 if (_rootQmlObject()) {
480 for (const QPair<QString, QString>& appMsg : _delayedAppMessages) {
481 showAppMessage(appMsg.second, appMsg.first);
482 }
483 _delayedAppMessages.clear();
484 } else {
485 QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages);
486 }
487}
488
490{
491 if (!_mainRootWindow) {
492 _mainRootWindow = qobject_cast<QQuickWindow*>(_rootQmlObject());
493 }
494
495 return _mainRootWindow;
496}
497
499{
500 if (_rootQmlObject()) {
501 QMetaObject::invokeMethod(_rootQmlObject(), "attemptWindowClose");
502 }
503}
504
506{
507 QSettings settings;
508 const QDir parameterDir = QFileInfo(settings.fileName()).dir();
509 return parameterDir.filePath(QStringLiteral("ParameterFactMetaData.json"));
510}
511
513{
514 QSettings settings;
515 const QDir airframeDir = QFileInfo(settings.fileName()).dir();
516 return airframeDir.filePath(QStringLiteral("PX4AirframeFactMetaData.xml"));
517}
518
519int QGCApplication::CompressedSignalList::_signalIndex(const QMetaMethod& method)
520{
521 if (method.methodType() != QMetaMethod::Signal) {
522 qCWarning(QGCApplicationLog) << "Internal error:" << Q_FUNC_INFO << "not a signal" << method.methodType();
523 return -1;
524 }
525
526 int index = -1;
527 const QMetaObject* metaObject = method.enclosingMetaObject();
528 for (int i = 0; i <= method.methodIndex(); i++) {
529 if (metaObject->method(i).methodType() != QMetaMethod::Signal) {
530 continue;
531 }
532 index++;
533 }
534
535 return index;
536}
537
538void QGCApplication::CompressedSignalList::add(const QMetaMethod& method)
539{
540 const QMetaObject* metaObject = method.enclosingMetaObject();
541 const int signalIndex = _signalIndex(method);
542
543 if (signalIndex != -1 && !contains(metaObject, signalIndex)) {
544 _signalMap[method.enclosingMetaObject()].insert(signalIndex);
545 }
546}
547
548void QGCApplication::CompressedSignalList::remove(const QMetaMethod& method)
549{
550 const int signalIndex = _signalIndex(method);
551 const QMetaObject* const metaObject = method.enclosingMetaObject();
552
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);
557 }
558 }
559}
560
561bool QGCApplication::CompressedSignalList::contains(const QMetaObject* metaObject, int signalIndex)
562{
563 return _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex);
564}
565
566void QGCApplication::addCompressedSignal(const QMetaMethod& method)
567{
568 _compressedSignals.add(method);
569}
570
571void QGCApplication::removeCompressedSignal(const QMetaMethod& method)
572{
573 _compressedSignals.remove(method);
574}
575
576QT_WARNING_PUSH
577
578QT_WARNING_DISABLE_DEPRECATED
579bool QGCApplication::compressEvent(QEvent* event, QObject* receiver, QPostEventList* postedEvents)
580{
581 if (event->type() != QEvent::MetaCall) {
582 return QGuiApplication::compressEvent(event, receiver, postedEvents);
583 }
584
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);
588 }
589
590 // QMetaCallEvent::id() was removed in 6.11; its protected Data is reachable from a derived helper.
591 struct MetaCallHelper : public QMetaCallEvent {
592 int id() const { return d.method_offset_ + d.method_relative_; }
593 };
594 const auto methodId = [](const QMetaCallEvent *e) { return static_cast<const MetaCallHelper*>(e)->id(); };
595
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()) {
599 continue;
600 }
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)) {
604 continue;
605 }
606
607 /* Keep The Newest Call */
608 // We can't merely qSwap the existing posted event with the new one, since QEvent
609 // keeps track of whether it has been posted. Deletion of a formerly posted event
610 // takes the posted event list mutex and does a useless search of the posted event
611 // list upon deletion. We thus clear the QEvent::posted flag before deletion.
612 struct EventHelper : private QEvent
613 {
614 static void clearPostedFlag(QEvent* ev)
615 {
616 (&static_cast<EventHelper*>(ev)->t)[1] &= ~0x8001; // Hack to clear QEvent::posted
617 }
618 };
619
620 EventHelper::clearPostedFlag(cur.event);
621 delete cur.event;
622 cur.event = event;
623 return true;
624 }
625
626 return false;
627}
628
629QT_WARNING_POP
630
632{
633 if (e->type() == QEvent::Quit) {
634 if (!_mainRootWindow) {
635 return QGuiApplication::event(e);
636 }
637 // On OSX if the user selects Quit from the menu (or Command-Q) the ApplicationWindow does not signal closing.
638 // Instead you get a Quit event here only. This in turn causes the standard QGC shutdown sequence to not run. So
639 // in this case we close the window ourselves such that the signal is sent and the normal shutdown sequence
640 // runs.
641 const bool forceClose = _mainRootWindow->property("_forceClose").toBool();
642 qCDebug(QGCApplicationLog) << "Quit event" << forceClose;
643 // forceClose
644 // true: Standard QGC shutdown sequence is complete. Let the app quit normally by falling through to the base
645 // class processing. false: QGC shutdown sequence has not been run yet. Don't let this event close the app
646 // yet. Close the main window to kick off the normal shutdown.
647 if (!forceClose) {
648 //
649 _mainRootWindow->close();
650 e->ignore();
651 return true;
652 }
653 }
654
655 return QGuiApplication::event(e);
656}
657
659{
660 return dynamic_cast<QGCImageProvider*>(_qmlAppEngine->imageProvider(_qgcImageProviderId));
661}
662
664{
666 qCDebug(QGCApplicationLog) << "Exit";
667
668 if (_videoManagerInitialized) {
670 }
671
672 // Engines from createQmlApplicationEngine must die through the destroy hook so the plugin
673 // can release per-engine state; parent-based teardown in ~QGCApplication would bypass it.
674 if (_qmlAppEngine) {
676 _qmlAppEngine = nullptr;
677 }
678
680
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;
687 } else {
688 qCWarning(QGCApplicationLog) << "Failed to remove test run settings file:" << settingsFile;
689 }
690 }
691
692 // Remove the app-specific settings directory (parent of ParamCache)
693 QDir settingsAppDir(ParameterManager::parameterCacheDir());
694 settingsAppDir.cdUp();
695 if (settingsAppDir.exists()) {
696 if (settingsAppDir.removeRecursively()) {
697 qCDebug(QGCApplicationLog) << "Removed test run settings directory:" << settingsAppDir.absolutePath();
698 } else {
699 qCWarning(QGCApplicationLog)
700 << "Failed to remove test run settings directory:" << settingsAppDir.absolutePath();
701 }
702 }
703
704 QDir appDir(SettingsManager::instance()->appSettings()->savePath()->rawValue().toString());
705 if (appDir.exists()) {
706 if (appDir.removeRecursively()) {
707 qCDebug(QGCApplicationLog) << "Removed test run app data directory:" << appDir.absolutePath();
708 } else {
709 qCWarning(QGCApplicationLog)
710 << "Failed to remove test run app data directory:" << appDir.absolutePath();
711 }
712 }
713 }
714
715 // This is bad, but currently qobject inheritances are incorrect and cause crashes on exit without
716 delete _qmlAppEngine;
717}
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static constexpr const char * clearSettingsNextBootKey
Definition AppSettings.h:62
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()
Definition FollowMe.cc:37
void init()
Definition FollowMe.cc:42
void shutdown()
Stops the GPS services, then the position manager.
static GPSManager * instance()
Definition GPSManager.cc:67
void init()
Definition GPSManager.cc:88
static JoystickManager * instance()
void loadLinkConfigurationList()
void startAutoConnectedLinks()
static LinkManager * instance()
void init()
static LogManager * instance()
Definition LogManager.cc:76
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 init()
virtual void cleanup()
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.