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/QRegularExpression>
8#include <QtCore/private/qthread_p.h>
9#include <QtGui/QFontDatabase>
10#include <QtGui/QIcon>
11#include <QtQml/QQmlApplicationEngine>
12#include <QtQml/QQmlContext>
13#include <QtQuick/QQuickImageProvider>
14#include <QtQuick/QQuickWindow>
15#include <QtQuickControls2/QQuickStyle>
16#include <QtSvg/QSvgRenderer>
17
18#include "AppSettings.h"
19#include "AudioOutput.h"
21#include "FollowMe.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 "NTRIPManager.h"
31#include "ParameterManager.h"
32#include "PositionManager.h"
34#include "QGCCorePlugin.h"
35#include "QGCFileDownload.h"
36#include "QGCImageProvider.h"
37#include "QGCLoggingCategory.h"
39#include "QGCNetworkHelper.h"
40#include "SettingsManager.h"
41#include "Vehicle.h"
42#include "VideoManager.h"
43#include "qgc_version.h"
44
45#ifndef QGC_NO_SERIAL_LINK
46#include "SerialLink.h"
47#endif
48
49QGC_LOGGING_CATEGORY(QGCApplicationLog, "API.QGCApplication")
50QGC_LOGGING_CATEGORY(QGCAppMessageLog, "API.QGCApplication.AppMessage")
51
52QGCApplication::QGCApplication(int& argc, char* argv[], const QGCCommandLineParser::CommandLineParseResult& cli)
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))
59{
60 _msecsElapsedTime.start();
61
62 // Setup for network proxy support
64
65 bool fClearSettingsOptions = cli.clearSettingsOptions; // Clear stored settings
66 const bool fClearCache = cli.clearCache; // Clear parameter/airframe caches
67 const QString loggingOptions = cli.loggingOptions.value_or(QString(""));
68
69 // Set up timer for delayed missing fact display
70 _missingParamsDelayedDisplayTimer.setSingleShot(true);
71 _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout);
72 (void) connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, &QGCApplication::_missingParamsDisplay);
73
74 // Set application information
75 QString applicationName;
76 if (_runningUnitTests || _simpleBootTest) {
77 // We don't want unit tests to use the same QSettings space as the normal app. So we tweak the app
78 // name. Also we want to run unit tests with clean settings every time.
79 // Include test name or PID to prevent settings file conflicts when tests run in parallel
80 if (!cli.unitTests.isEmpty()) {
81 applicationName = QStringLiteral("%1_unittest_%2").arg(QGC_APP_NAME, cli.unitTests.first());
82 } else {
83 applicationName =
84 QStringLiteral("%1_unittest_%2").arg(QGC_APP_NAME).arg(QCoreApplication::applicationPid());
85 }
86 } else {
87#ifdef QGC_DAILY_BUILD
88 // This gives daily builds their own separate settings space. Allowing you to use daily and stable builds
89 // side by side without daily screwing up your stable settings.
90 applicationName = QStringLiteral("%1 Daily").arg(QGC_APP_NAME);
91#else
92 applicationName = QGC_APP_NAME;
93#endif
94 }
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));
100
101 // Set settings format
102 QSettings::setDefaultFormat(QSettings::IniFormat);
103 QSettings settings;
104 qCDebug(QGCApplicationLog) << "Settings location" << settings.fileName()
105 << "Is writable?:" << settings.isWritable();
106
107 if (!settings.isWritable()) {
108 qCWarning(QGCApplicationLog) << "Setings location is not writable";
109 }
110
111 // The setting will delete all settings on this boot
112 fClearSettingsOptions |= settings.value(AppSettings::clearSettingsNextBootKey, false).toBool();
113
114 if (_runningUnitTests || _simpleBootTest) {
115 // Unit tests run with clean settings
116 fClearSettingsOptions = true;
117 }
118
119 if (fClearSettingsOptions) {
120 // User requested settings to be cleared on command line
121 settings.clear();
122
123 // Clear parameter cache
125 paramDir.removeRecursively();
126 paramDir.mkpath(paramDir.absolutePath());
127 } else {
128 // Determine if upgrade message for settings version bump is required. Check and clear must happen before
129 // toolbox is started since that will write some settings.
130 if (settings.contains(_settingsVersionKey)) {
131 if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) {
132 settings.clear();
133 _settingsUpgraded = true;
134 }
135 }
136 }
137 settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
138
139 if (fClearCache) {
141 dir.removeRecursively();
142 QFile parameter(cachedParameterMetaDataFile());
143 parameter.remove();
144 QFile airframe(cachedAirframeMetaDataFile());
145 airframe.remove();
146
147 // Clear versioned parameter metadata cache
148 const QString metaDataCachePath =
149 QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/ParameterMetaData");
150 QDir(metaDataCachePath).removeRecursively();
151 }
152
153 // Set up our logging filters
156
157 if (_runningUnitTests) {
158 // Enable the AppMessage category so showAppMessage() debug logs are visible during unit tests.
159 QGCLoggingCategoryManager::instance()->setCategoryEnabled(QStringLiteral("API.QGCApplication.AppMessage"),
160 true);
161 }
162
163 // We need to set language as early as possible prior to loading on JSON files.
164 setLanguage();
165
166 // Force old SVG Tiny 1.2 behavior for compatibility
167 QSvgRenderer::setDefaultOptions(QtSvg::Tiny12FeaturesOnly);
168
169#ifndef QGC_DAILY_BUILD
170 _checkForNewVersion();
171#endif
172}
173
175{
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]
180 : "None");
181
182 QLocale::Language possibleLocale = AppSettings::_qLocaleLanguageEarlyAccess();
183 if (possibleLocale != QLocale::AnyLanguage) {
184 _locale = QLocale(possibleLocale);
185 }
186 //-- We have specific fonts for Korean
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";
191 }
192 if (QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Bold") < 0) {
193 qCWarning(QGCApplicationLog) << "Could not load /fonts/NanumGothic-Bold font";
194 }
195 }
196 qCDebug(QGCApplicationLog) << "Loading localizations for" << _locale.name();
197 removeTranslator(JsonParsing::translator());
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);
204 } else {
205 qCWarning(QGCApplicationLog) << "Qt lib localization for" << _locale.name() << "is not present";
206 }
207 if (_qgcTranslatorSourceCode.load(_locale, QLatin1String("qgc_source_"), "", ":/i18n")) {
208 installTranslator(&_qgcTranslatorSourceCode);
209 } else {
210 qCWarning(QGCApplicationLog) << "Error loading source localization for" << _locale.name();
211 }
212 if (JsonParsing::translator()->load(_locale, QLatin1String("qgc_json_"), "", ":/i18n")) {
213 installTranslator(JsonParsing::translator());
214 } else {
215 qCWarning(QGCApplicationLog) << "Error loading json localization for" << _locale.name();
216 }
217 }
218
219 if (_qmlAppEngine) {
220 _qmlAppEngine->retranslate();
221 }
222
223 emit languageChanged(_locale);
224}
225
227
229{
231 if (_systemId > 0) {
232 qCDebug(QGCApplicationLog) << "Setting MAVLink System ID to:" << _systemId;
233 SettingsManager::instance()->mavlinkSettings()->gcsMavlinkSystemID()->setRawValue(_systemId);
234 }
235
237
238 // Although this should really be in _initForNormalAppBoot putting it here allowws us to create unit tests which pop
239 // up more easily
240 if (QFontDatabase::addApplicationFont(":/fonts/opensans") < 0) {
241 qCWarning(QGCApplicationLog) << "Could not load /fonts/opensans font";
242 }
243
244 if (QFontDatabase::addApplicationFont(":/fonts/opensans-demibold") < 0) {
245 qCWarning(QGCApplicationLog) << "Could not load /fonts/opensans-demibold font";
246 }
247
248 if (_simpleBootTest) {
249 // Since GStream builds are so problematic we initialize video during the simple boot test
250 // to make sure it works and verfies plugin availability.
251 const bool videoInitialized = _initVideo();
252 const bool qmlRootLoaded = _initQmlRootWindow();
253 _bootTestPassed = videoInitialized && qmlRootLoaded;
254 } else if (!_runningUnitTests) {
255 _initForNormalAppBoot();
256 }
257}
258
259bool QGCApplication::_initVideo()
260{
261#ifdef QGC_GST_STREAMING
262 qCDebug(QGCApplicationLog) << "Using default graphics API for appsink → VideoOutput video path";
263#endif
264
265 QGCCorePlugin::instance(); // CorePlugin must be initialized before VideoManager for Video Cleanup
266 VideoManager* videoManager = VideoManager::instance();
267 videoManager->startVideoBackendInit();
268 const bool initSucceeded = !_simpleBootTest || videoManager->waitForVideoBackendReady();
269 _videoManagerInitialized = true;
270 return initSucceeded;
271}
272
273bool QGCApplication::_initQmlRootWindow()
274{
275 QQuickStyle::setStyle("Basic");
280 QObject::connect(_qmlAppEngine, &QQmlApplicationEngine::objectCreationFailed, this, QCoreApplication::quit,
281 Qt::QueuedConnection);
282
283 // Must register before createRootWindow — root QML references QGCColoredImage which resolves image://coloredsvg/...
284 // at load time.
285 _qmlAppEngine->addImageProvider(_qgcImageProviderId, new QGCImageProvider());
286 _qmlAppEngine->addImageProvider(QLatin1String(ColoredSvgImageProvider::ProviderId), new ColoredSvgImageProvider());
287
289
290 // The root QQuickWindow exists now (load() is synchronous) but its scene graph has not been
291 // initialized yet -- the only safe point to apply RHI graphics config / forced device.
293
294 return mainRootWindow() != nullptr;
295}
296
297void QGCApplication::_initForNormalAppBoot()
298{
299 (void) _initVideo();
300
301 (void) _initQmlRootWindow();
302
303 AudioOutput::instance()->init(SettingsManager::instance()->appSettings()->audioVolume(),
304 SettingsManager::instance()->appSettings()->audioMuted());
310
311 // Set the window icon now that custom plugin has a chance to override it
312#ifdef Q_OS_LINUX
313 QUrl windowIcon = QUrl("qrc:/res/qgroundcontrol.ico");
314 windowIcon = _qmlAppEngine->interceptUrl(windowIcon, QQmlAbstractUrlInterceptor::UrlString);
315 // The interceptor needs "qrc:/path" but QIcon expects ":/path"
316 setWindowIcon(QIcon(":" + windowIcon.path()));
317#endif
318
319 // Safe to show popup error messages now that main window is created
320 _showErrorsInToolbar = true;
321
322#ifdef Q_OS_LINUX
323#ifndef Q_OS_ANDROID
324#ifndef QGC_NO_SERIAL_LINK
325 if (!_runningUnitTests) {
326 // Determine if we have the correct permissions to access USB serial devices
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"))) {
332 permFile.close();
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>"));
339 break;
340 }
341 }
342 permFile.close();
343 }
344 }
345#endif
346#endif
347#endif
348
349 // Now that main window is up check for lost log files
351
352 // Load known link configurations
354
355 // Probe for joysticks
357
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()));
362 }
363
364 // Connect links with flag AutoconnectLink
366}
367
368void QGCApplication::reportMissingParameter(int componentId, const QString& name)
369{
370 const QPair<int, QString> missingParam(componentId, name);
371
372 if (!_missingParams.contains(missingParam)) {
373 _missingParams.append(missingParam);
374 }
375 _missingParamsDelayedDisplayTimer.start();
376}
377
378void QGCApplication::_missingParamsDisplay()
379{
380 if (_missingParams.isEmpty()) {
381 return;
382 }
383
384 QString params;
385 for (QPair<int, QString>& missingParam : _missingParams) {
386 const QString param = QStringLiteral("%1:%2").arg(missingParam.first).arg(missingParam.second);
387 if (params.isEmpty()) {
388 params += param;
389 } else {
390 params += QStringLiteral(", %1").arg(param);
391 }
392 }
393 _missingParams.clear();
394
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")
397 .arg(params));
398}
399
400QObject* QGCApplication::_rootQmlObject()
401{
402 if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) {
403 return _qmlAppEngine->rootObjects()[0];
404 }
405
406 return nullptr;
407}
408
410{
411 // PreArm messages are handled by Vehicle and shown in Map
412 if (message.startsWith(QStringLiteral("PreArm")) ||
413 message.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive)) {
414 return;
415 }
416
417 QObject* const rootQmlObject = _rootQmlObject();
418 if (rootQmlObject && _showErrorsInToolbar) {
419 QVariant varReturn;
420 QVariant varMessage = QVariant::fromValue(message);
421 QMetaObject::invokeMethod(rootQmlObject, "showCriticalVehicleMessage", Q_RETURN_ARG(QVariant, varReturn),
422 Q_ARG(QVariant, varMessage));
423 } else if (runningUnitTests() || !_showErrorsInToolbar) {
424 // Unit tests can run without UI
425 qCDebug(QGCApplicationLog) << "QGCApplication::showCriticalVehicleMessage unittest" << message;
426 } else {
427 qCWarning(QGCApplicationLog) << "Internal error";
428 }
429}
430
431void QGCApplication::showAppMessage(const QString& message, const QString& title)
432{
433 const QString dialogTitle = title.isEmpty() ? applicationName() : title;
434
435 if (runningUnitTests()) {
436 // Logged under QGCAppMessageLog so tests can assert expected dialogs via
437 // expectAppMessage() without matching against the general QGCApplication category.
438 qCDebug(QGCAppMessageLog) << "showAppMessage:" << dialogTitle << "-" << message;
439 if (!_uiTestMode) {
440 // Headless test: there is no QML root to host the dialog and the delayed-message
441 // timer would retry forever, so the log is the only record.
442 return;
443 }
444 // UI tests fall through: the message dialog is a non-blocking QML popup, so show it
445 // for real and let the test handle it the same way a user would.
446 }
447
448 QObject* const rootQmlObject = _rootQmlObject();
449 if (rootQmlObject) {
450 QVariant varReturn;
451 QVariant varMessage = QVariant::fromValue(message);
452 QMetaObject::invokeMethod(rootQmlObject, "_showMessageDialog", Q_RETURN_ARG(QVariant, varReturn),
453 Q_ARG(QVariant, dialogTitle), Q_ARG(QVariant, varMessage));
454 } else {
455 // UI isn't ready yet
456 _delayedAppMessages.append(QPair<QString, QString>(dialogTitle, message));
457 QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages);
458 }
459}
460
461bool QGCApplication::_rebootMessageDebounced()
462{
463 const QTime currentTime = QTime::currentTime();
464 const QTime previousTime = _lastRebootMessageTime;
465 _lastRebootMessageTime = currentTime;
466
467 return previousTime.isValid() && (previousTime.msecsTo(currentTime) < (60 * 1000 * 2));
468}
469
470void QGCApplication::showRebootAppMessage(const QString& message, const QString& title)
471{
472 if (_rebootMessageDebounced()) {
473 return;
474 }
475
476 showAppMessage(message, title);
477}
478
479void QGCApplication::showRebootVehicleMessage(const QString& message, const QString& title)
480{
481 if (_rebootMessageDebounced()) {
482 return;
483 }
484
485 const QString dialogTitle = title.isEmpty() ? applicationName() : title;
486
487 if (runningUnitTests()) {
488 // Same log format as showAppMessage() so tests assert this via expectAppMessage()
489 qCDebug(QGCAppMessageLog) << "showAppMessage:" << dialogTitle << "-" << message;
490 if (!_uiTestMode) {
491 return;
492 }
493 }
494
495 QObject* const rootQmlObject = _rootQmlObject();
496 if (rootQmlObject) {
497 QVariant varReturn;
498 QVariant varMessage = QVariant::fromValue(message);
499 QMetaObject::invokeMethod(rootQmlObject, "_showRebootVehicleDialog", Q_RETURN_ARG(QVariant, varReturn),
500 Q_ARG(QVariant, dialogTitle), Q_ARG(QVariant, varMessage));
501 } else {
502 // UI isn't ready yet: fall back to the plain app message queue
503 _delayedAppMessages.append(QPair<QString, QString>(dialogTitle, message));
504 QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages);
505 }
506}
507
508void QGCApplication::_showDelayedAppMessages()
509{
510 if (_rootQmlObject()) {
511 for (const QPair<QString, QString>& appMsg : _delayedAppMessages) {
512 showAppMessage(appMsg.second, appMsg.first);
513 }
514 _delayedAppMessages.clear();
515 } else {
516 QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages);
517 }
518}
519
521{
522 if (!_mainRootWindow) {
523 _mainRootWindow = qobject_cast<QQuickWindow*>(_rootQmlObject());
524 }
525
526 return _mainRootWindow;
527}
528
530{
531 if (_rootQmlObject()) {
532 QMetaObject::invokeMethod(_rootQmlObject(), "attemptWindowClose");
533 }
534}
535
536void QGCApplication::_checkForNewVersion()
537{
538 if (_runningUnitTests) {
539 return;
540 }
541
542 if (!_parseVersionText(applicationVersion(), _majorVersion, _minorVersion, _buildVersion)) {
543 return;
544 }
545
546 const QString versionCheckFile = QGCCorePlugin::instance()->stableVersionCheckFileUrl();
547 if (!versionCheckFile.isEmpty()) {
548 QGCFileDownload* const download = new QGCFileDownload(this);
549 (void) connect(download, &QGCFileDownload::finished, this,
550 &QGCApplication::_qgcCurrentStableVersionDownloadComplete);
551 if (!download->start(versionCheckFile)) {
552 qCDebug(QGCApplicationLog) << "Download QGC stable version failed to start" << download->errorString();
553 download->deleteLater();
554 }
555 }
556}
557
558void QGCApplication::_qgcCurrentStableVersionDownloadComplete(bool success, const QString& localFile,
559 const QString& errorMsg)
560{
561 if (success) {
562 QFile versionFile(localFile);
563 if (versionFile.open(QIODevice::ReadOnly)) {
564 QTextStream textStream(&versionFile);
565 const QString version = textStream.readLine();
566
567 qCDebug(QGCApplicationLog) << version;
568
569 int majorVersion, minorVersion, buildVersion;
570 if (_parseVersionText(version, majorVersion, minorVersion, buildVersion)) {
571 if (_majorVersion < majorVersion ||
572 ((_majorVersion == majorVersion) && (_minorVersion < minorVersion)) ||
573 ((_majorVersion == majorVersion) && (_minorVersion == minorVersion) &&
574 (_buildVersion < buildVersion))) {
575 showAppMessage(tr("There is a newer version of %1 available. You can download it from %2.")
576 .arg(applicationName())
577 .arg(QGCCorePlugin::instance()->stableDownloadLocation()),
578 tr("New Version Available"));
579 }
580 }
581 }
582 } else if (!errorMsg.isEmpty()) {
583 qCDebug(QGCApplicationLog) << "Download QGC stable version failed" << errorMsg;
584 }
585
586 sender()->deleteLater();
587}
588
589bool QGCApplication::_parseVersionText(const QString& versionString, int& majorVersion, int& minorVersion,
590 int& buildVersion)
591{
592 static const QRegularExpression regExp("v(\\d+)\\.(\\d+)\\.(\\d+)");
593 const QRegularExpressionMatch match = regExp.match(versionString);
594 if (match.hasMatch() && match.lastCapturedIndex() == 3) {
595 majorVersion = match.captured(1).toInt();
596 minorVersion = match.captured(2).toInt();
597 buildVersion = match.captured(3).toInt();
598 return true;
599 }
600
601 return false;
602}
603
605{
606 QSettings settings;
607 const QDir parameterDir = QFileInfo(settings.fileName()).dir();
608 return parameterDir.filePath(QStringLiteral("ParameterFactMetaData.json"));
609}
610
612{
613 QSettings settings;
614 const QDir airframeDir = QFileInfo(settings.fileName()).dir();
615 return airframeDir.filePath(QStringLiteral("PX4AirframeFactMetaData.xml"));
616}
617
618int QGCApplication::CompressedSignalList::_signalIndex(const QMetaMethod& method)
619{
620 if (method.methodType() != QMetaMethod::Signal) {
621 qCWarning(QGCApplicationLog) << "Internal error:" << Q_FUNC_INFO << "not a signal" << method.methodType();
622 return -1;
623 }
624
625 int index = -1;
626 const QMetaObject* metaObject = method.enclosingMetaObject();
627 for (int i = 0; i <= method.methodIndex(); i++) {
628 if (metaObject->method(i).methodType() != QMetaMethod::Signal) {
629 continue;
630 }
631 index++;
632 }
633
634 return index;
635}
636
637void QGCApplication::CompressedSignalList::add(const QMetaMethod& method)
638{
639 const QMetaObject* metaObject = method.enclosingMetaObject();
640 const int signalIndex = _signalIndex(method);
641
642 if (signalIndex != -1 && !contains(metaObject, signalIndex)) {
643 _signalMap[method.enclosingMetaObject()].insert(signalIndex);
644 }
645}
646
647void QGCApplication::CompressedSignalList::remove(const QMetaMethod& method)
648{
649 const int signalIndex = _signalIndex(method);
650 const QMetaObject* const metaObject = method.enclosingMetaObject();
651
652 if (signalIndex != -1 && _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex)) {
653 _signalMap[metaObject].remove(signalIndex);
654 if (_signalMap[metaObject].count() == 0) {
655 _signalMap.remove(metaObject);
656 }
657 }
658}
659
660bool QGCApplication::CompressedSignalList::contains(const QMetaObject* metaObject, int signalIndex)
661{
662 return _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex);
663}
664
665void QGCApplication::addCompressedSignal(const QMetaMethod& method)
666{
667 _compressedSignals.add(method);
668}
669
670void QGCApplication::removeCompressedSignal(const QMetaMethod& method)
671{
672 _compressedSignals.remove(method);
673}
674
675QT_WARNING_PUSH
676
677QT_WARNING_DISABLE_DEPRECATED
678bool QGCApplication::compressEvent(QEvent* event, QObject* receiver, QPostEventList* postedEvents)
679{
680 if (event->type() != QEvent::MetaCall) {
681 return QGuiApplication::compressEvent(event, receiver, postedEvents);
682 }
683
684 const QMetaCallEvent* mce = static_cast<QMetaCallEvent*>(event);
685 if (!mce->sender() || !_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) {
686 return QGuiApplication::compressEvent(event, receiver, postedEvents);
687 }
688
689 // QMetaCallEvent::id() was removed in 6.11; its protected Data is reachable from a derived helper.
690 struct MetaCallHelper : public QMetaCallEvent {
691 int id() const { return d.method_offset_ + d.method_relative_; }
692 };
693 const auto methodId = [](const QMetaCallEvent *e) { return static_cast<const MetaCallHelper*>(e)->id(); };
694
695 for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) {
696 QPostEvent& cur = *it;
697 if (cur.receiver != receiver || cur.event == 0 || cur.event->type() != event->type()) {
698 continue;
699 }
700 const QMetaCallEvent* cur_mce = static_cast<QMetaCallEvent*>(cur.event);
701 if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() ||
702 methodId(cur_mce) != methodId(mce)) {
703 continue;
704 }
705
706 /* Keep The Newest Call */
707 // We can't merely qSwap the existing posted event with the new one, since QEvent
708 // keeps track of whether it has been posted. Deletion of a formerly posted event
709 // takes the posted event list mutex and does a useless search of the posted event
710 // list upon deletion. We thus clear the QEvent::posted flag before deletion.
711 struct EventHelper : private QEvent
712 {
713 static void clearPostedFlag(QEvent* ev)
714 {
715 (&static_cast<EventHelper*>(ev)->t)[1] &= ~0x8001; // Hack to clear QEvent::posted
716 }
717 };
718
719 EventHelper::clearPostedFlag(cur.event);
720 delete cur.event;
721 cur.event = event;
722 return true;
723 }
724
725 return false;
726}
727
728QT_WARNING_POP
729
731{
732 if (e->type() == QEvent::Quit) {
733 if (!_mainRootWindow) {
734 return QGuiApplication::event(e);
735 }
736 // On OSX if the user selects Quit from the menu (or Command-Q) the ApplicationWindow does not signal closing.
737 // Instead you get a Quit event here only. This in turn causes the standard QGC shutdown sequence to not run. So
738 // in this case we close the window ourselves such that the signal is sent and the normal shutdown sequence
739 // runs.
740 const bool forceClose = _mainRootWindow->property("_forceClose").toBool();
741 qCDebug(QGCApplicationLog) << "Quit event" << forceClose;
742 // forceClose
743 // true: Standard QGC shutdown sequence is complete. Let the app quit normally by falling through to the base
744 // class processing. false: QGC shutdown sequence has not been run yet. Don't let this event close the app
745 // yet. Close the main window to kick off the normal shutdown.
746 if (!forceClose) {
747 //
748 _mainRootWindow->close();
749 e->ignore();
750 return true;
751 }
752 }
753
754 return QGuiApplication::event(e);
755}
756
758{
759 return dynamic_cast<QGCImageProvider*>(_qmlAppEngine->imageProvider(_qgcImageProviderId));
760}
761
763{
764 qCDebug(QGCApplicationLog) << "Exit";
765
766 if (_videoManagerInitialized) {
768 }
769
770 // Engines from createQmlApplicationEngine must die through the destroy hook so the plugin
771 // can release per-engine state; parent-based teardown in ~QGCApplication would bypass it.
772 if (_qmlAppEngine) {
774 _qmlAppEngine = nullptr;
775 }
776
778
779 if (_runningUnitTests || _simpleBootTest) {
780 const QSettings settings;
781 const QString settingsFile = settings.fileName();
782 if (QFile::exists(settingsFile)) {
783 if (QFile::remove(settingsFile)) {
784 qCDebug(QGCApplicationLog) << "Removed test run settings file:" << settingsFile;
785 } else {
786 qCWarning(QGCApplicationLog) << "Failed to remove test run settings file:" << settingsFile;
787 }
788 }
789
790 // Remove the app-specific settings directory (parent of ParamCache)
791 QDir settingsAppDir(ParameterManager::parameterCacheDir());
792 settingsAppDir.cdUp();
793 if (settingsAppDir.exists()) {
794 if (settingsAppDir.removeRecursively()) {
795 qCDebug(QGCApplicationLog) << "Removed test run settings directory:" << settingsAppDir.absolutePath();
796 } else {
797 qCWarning(QGCApplicationLog)
798 << "Failed to remove test run settings directory:" << settingsAppDir.absolutePath();
799 }
800 }
801
802 QDir appDir(SettingsManager::instance()->appSettings()->savePath()->rawValue().toString());
803 if (appDir.exists()) {
804 if (appDir.removeRecursively()) {
805 qCDebug(QGCApplicationLog) << "Removed test run app data directory:" << appDir.absolutePath();
806 } else {
807 qCWarning(QGCApplicationLog)
808 << "Failed to remove test run app data directory:" << appDir.absolutePath();
809 }
810 }
811 }
812
813 // This is bad, but currently qobject inheritances are incorrect and cause crashes on exit without
814 delete _qmlAppEngine;
815}
Unified file download utility with decompression, verification, and QML support.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static constexpr const char * clearSettingsNextBootKey
Definition AppSettings.h:61
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:35
void init()
Definition FollowMe.cc:40
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 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.
void showRebootVehicleMessage(const QString &message, const QString &title=QString())
Same as showRebootAppMessage() but the dialog also includes a button which reboots the active vehicle...
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
virtual void init()
virtual void cleanup()
virtual void destroyQmlApplicationEngine(QQmlApplicationEngine *qmlEngine)
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.