QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCApplication.cc
Go to the documentation of this file.
1#include "JsonParsing.h"
2#include "QGCApplication.h"
3#include "qgc_version.h"
4
5#include <QtCore/QEvent>
6#include <QtCore/QFile>
7#include <QtCore/QMetaMethod>
8#include <QtCore/QMetaObject>
9#include <QtCore/QRegularExpression>
10#include <QtGui/QFontDatabase>
11#include <QtGui/QIcon>
12#include "QGCNetworkHelper.h"
13#include <QtQml/QQmlApplicationEngine>
14#include <QtQml/QQmlContext>
15#include <QtQuick/QQuickImageProvider>
16#include <QtQuick/QQuickWindow>
17#include <QtQuickControls2/QQuickStyle>
18#include <QtSvg/QSvgRenderer>
19
20#include <QtCore/private/qthread_p.h>
21
22#include "LogManager.h"
23#include "LogRemoteSink.h"
24#include "AudioOutput.h"
25#include "FollowMe.h"
26#include "JoystickManager.h"
27#include "LinkManager.h"
28#include "MAVLinkProtocol.h"
29#include "MultiVehicleManager.h"
30#include "ParameterManager.h"
31#include "PositionManager.h"
33#include "QGCCorePlugin.h"
34#include "QGCFileDownload.h"
36#include "QGCImageProvider.h"
37#include "QGCLoggingCategory.h"
39#include "SettingsManager.h"
40#include "MavlinkSettings.h"
41#include "AppSettings.h"
42#include "UDPLink.h"
43#include "Vehicle.h"
44#include "VehicleComponent.h"
45#include "VideoManager.h"
46
47#ifndef QGC_NO_SERIAL_LINK
48#include "SerialLink.h"
49#endif
50
51QGC_LOGGING_CATEGORY(QGCApplicationLog, "API.QGCApplication")
52
53QGCApplication::QGCApplication(int &argc, char *argv[], const QGCCommandLineParser::CommandLineParseResult &cli)
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))
60{
61 _msecsElapsedTime.start();
62
63 // Setup for network proxy support
65
66 bool fClearSettingsOptions = cli.clearSettingsOptions; // Clear stored settings
67 const bool fClearCache = cli.clearCache; // Clear parameter/airframe caches
68 const QString loggingOptions = cli.loggingOptions.value_or(QString(""));
69
70 // Set up timer for delayed missing fact display
71 _missingParamsDelayedDisplayTimer.setSingleShot(true);
72 _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout);
73 (void) connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, &QGCApplication::_missingParamsDisplay);
74
75 // Set application information
76 QString applicationName;
77 if (_runningUnitTests || _simpleBootTest) {
78 // We don't want unit tests to use the same QSettings space as the normal app. So we tweak the app
79 // name. Also we want to run unit tests with clean settings every time.
80 // Include test name or PID to prevent settings file conflicts when tests run in parallel
81 if (!cli.unitTests.isEmpty()) {
82 applicationName = QStringLiteral("%1_unittest_%2").arg(QGC_APP_NAME, cli.unitTests.first());
83 } else {
84 applicationName = 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() << "Is writable?:" << settings.isWritable();
105
106 if (!settings.isWritable()) {
107 qCWarning(QGCApplicationLog) << "Setings location is not writable";
108 }
109
110 // The setting will delete all settings on this boot
111 fClearSettingsOptions |= settings.value(AppSettings::clearSettingsNextBootKey, false).toBool();
112
113 if (_runningUnitTests || _simpleBootTest) {
114 // Unit tests run with clean settings
115 fClearSettingsOptions = true;
116 }
117
118 if (fClearSettingsOptions) {
119 // User requested settings to be cleared on command line
120 settings.clear();
121
122 // Clear parameter cache
124 paramDir.removeRecursively();
125 paramDir.mkpath(paramDir.absolutePath());
126 } else {
127 // Determine if upgrade message for settings version bump is required. Check and clear must happen before toolbox is started since
128 // that will write some settings.
129 if (settings.contains(_settingsVersionKey)) {
130 if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) {
131 settings.clear();
132 _settingsUpgraded = true;
133 }
134 }
135 }
136 settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
137
138 if (fClearCache) {
140 dir.removeRecursively();
141 QFile parameter(cachedParameterMetaDataFile());
142 parameter.remove();
143 QFile airframe(cachedAirframeMetaDataFile());
144 airframe.remove();
145
146 // Clear versioned parameter metadata cache
147 const QString metaDataCachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
148 + QStringLiteral("/ParameterMetaData");
149 QDir(metaDataCachePath).removeRecursively();
150 }
151
152 // Set up our logging filters
155
156 // We need to set language as early as possible prior to loading on JSON files.
157 setLanguage();
158
159 // Force old SVG Tiny 1.2 behavior for compatibility
160 QSvgRenderer::setDefaultOptions(QtSvg::Tiny12FeaturesOnly);
161
162#ifndef QGC_DAILY_BUILD
163 _checkForNewVersion();
164#endif
165}
166
168{
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");
171
172 QLocale::Language possibleLocale = AppSettings::_qLocaleLanguageEarlyAccess();
173 if (possibleLocale != QLocale::AnyLanguage) {
174 _locale = QLocale(possibleLocale);
175 }
176 //-- We have specific fonts for Korean
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";
181 }
182 if(QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Bold") < 0) {
183 qCWarning(QGCApplicationLog) << "Could not load /fonts/NanumGothic-Bold font";
184 }
185 }
186 qCDebug(QGCApplicationLog) << "Loading localizations for" << _locale.name();
187 removeTranslator(JsonParsing::translator());
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);
194 } else {
195 qCWarning(QGCApplicationLog) << "Qt lib localization for" << _locale.name() << "is not present";
196 }
197 if (_qgcTranslatorSourceCode.load(_locale, QLatin1String("qgc_source_"), "", ":/i18n")) {
198 installTranslator(&_qgcTranslatorSourceCode);
199 } else {
200 qCWarning(QGCApplicationLog) << "Error loading source localization for" << _locale.name();
201 }
202 if (JsonParsing::translator()->load(_locale, QLatin1String("qgc_json_"), "", ":/i18n")) {
203 installTranslator(JsonParsing::translator());
204 } else {
205 qCWarning(QGCApplicationLog) << "Error loading json localization for" << _locale.name();
206 }
207 }
208
209 if (_qmlAppEngine) {
210 _qmlAppEngine->retranslate();
211 }
212
213 emit languageChanged(_locale);
214}
215
220
222{
224 if (_systemId > 0) {
225 qCDebug(QGCApplicationLog) << "Setting MAVLink System ID to:" << _systemId;
226 SettingsManager::instance()->mavlinkSettings()->gcsMavlinkSystemID()->setRawValue(_systemId);
227 }
228
229 // Set up log directory for disk logging and SQLite store, and re-apply on path change
230 {
231 auto *logMgr = LogManager::instance();
232 auto *appSettings = SettingsManager::instance()->appSettings();
233 logMgr->setLogDirectory(appSettings->logSavePath());
234 QObject::connect(appSettings, &AppSettings::savePathsChanged, logMgr, [logMgr, appSettings]() {
235 logMgr->setLogDirectory(appSettings->logSavePath());
236 });
237 }
238
239 // Wire remote logging settings to the sink
240 {
241 auto *appSettings = SettingsManager::instance()->appSettings();
242 auto *sink = LogManager::instance()->remoteSink();
243 auto applySetting = [appSettings, sink]() {
244 sink->setHost(appSettings->remoteLoggingHost()->rawValue().toString());
245 sink->setPort(static_cast<quint16>(appSettings->remoteLoggingPort()->rawValue().toUInt()));
246 sink->setProtocol(static_cast<TransportStrategy::Protocol>(
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());
254 };
255 for (auto *fact : {
256 appSettings->remoteLoggingEnabled(), appSettings->remoteLoggingHost(),
257 appSettings->remoteLoggingPort(), appSettings->remoteLoggingProtocol(),
258 appSettings->remoteLoggingVehicleId(), appSettings->remoteLoggingTlsEnabled(),
259 appSettings->remoteLoggingTlsVerifyPeer(),
260 appSettings->remoteLoggingCompressionEnabled(),
261 appSettings->remoteLoggingCompressionLevel(),
262 }) {
263 QObject::connect(fact, &Fact::rawValueChanged, sink, applySetting);
264 }
265 applySetting();
266 }
267
268 // Although this should really be in _initForNormalAppBoot putting it here allowws us to create unit tests which pop up more easily
269 if (QFontDatabase::addApplicationFont(":/fonts/opensans") < 0) {
270 qCWarning(QGCApplicationLog) << "Could not load /fonts/opensans font";
271 }
272
273 if (QFontDatabase::addApplicationFont(":/fonts/opensans-demibold") < 0) {
274 qCWarning(QGCApplicationLog) << "Could not load /fonts/opensans-demibold font";
275 }
276
277 if (_simpleBootTest) {
278 // Since GStream builds are so problematic we initialize video during the simple boot test
279 // to make sure it works and verfies plugin availability.
280 _bootTestPassed = _initVideo();
281 } else if (!_runningUnitTests) {
282 _initForNormalAppBoot();
283 }
284}
285
286bool QGCApplication::_initVideo()
287{
288#ifdef QGC_GST_STREAMING
289 qCDebug(QGCApplicationLog) << "Using default graphics API for appsink → VideoOutput video path";
290#endif
291
292 QGCCorePlugin::instance(); // CorePlugin must be initialized before VideoManager for Video Cleanup
293 VideoManager *videoManager = VideoManager::instance();
294 videoManager->startGStreamerInit();
295 const bool initSucceeded = !_simpleBootTest || videoManager->waitForGStreamerInit();
296 _videoManagerInitialized = true;
297 return initSucceeded;
298}
299
300void QGCApplication::_initForNormalAppBoot()
301{
302 (void) _initVideo();
303
304 QQuickStyle::setStyle("Basic");
309 QObject::connect(_qmlAppEngine, &QQmlApplicationEngine::objectCreationFailed, this, QCoreApplication::quit, Qt::QueuedConnection);
310
311 // Must register before createRootWindow — root QML references QGCColoredImage which resolves image://coloredsvg/... at load time.
312 _qmlAppEngine->addImageProvider(_qgcImageProviderId, new QGCImageProvider());
313 _qmlAppEngine->addImageProvider(QLatin1String(ColoredSvgImageProvider::ProviderId), new ColoredSvgImageProvider());
314
316
317 AudioOutput::instance()->init(SettingsManager::instance()->appSettings()->audioVolume(), SettingsManager::instance()->appSettings()->audioMuted());
322
323 // Set the window icon now that custom plugin has a chance to override it
324#ifdef Q_OS_LINUX
325 QUrl windowIcon = QUrl("qrc:/res/qgroundcontrol.ico");
326 windowIcon = _qmlAppEngine->interceptUrl(windowIcon, QQmlAbstractUrlInterceptor::UrlString);
327 // The interceptor needs "qrc:/path" but QIcon expects ":/path"
328 setWindowIcon(QIcon(":" + windowIcon.path()));
329#endif
330
331 // Safe to show popup error messages now that main window is created
332 _showErrorsInToolbar = true;
333
334 #ifdef Q_OS_LINUX
335 #ifndef Q_OS_ANDROID
336 #ifndef QGC_NO_SERIAL_LINK
337 if (!_runningUnitTests) {
338 // Determine if we have the correct permissions to access USB serial devices
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"))) {
344 permFile.close();
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>"));
351 break;
352 }
353 }
354 permFile.close();
355 }
356 }
357 #endif
358 #endif
359 #endif
360
361 // Now that main window is up check for lost log files
363
364 // Load known link configurations
366
367 // Probe for joysticks
369
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()));
373 }
374
375 // Connect links with flag AutoconnectLink
377}
378
379void QGCApplication::reportMissingParameter(int componentId, const QString &name)
380{
381 const QPair<int, QString> missingParam(componentId, name);
382
383 if (!_missingParams.contains(missingParam)) {
384 _missingParams.append(missingParam);
385 }
386 _missingParamsDelayedDisplayTimer.start();
387}
388
389void QGCApplication::_missingParamsDisplay()
390{
391 if (_missingParams.isEmpty()) {
392 return;
393 }
394
395 QString params;
396 for (QPair<int, QString>& missingParam: _missingParams) {
397 const QString param = QStringLiteral("%1:%2").arg(missingParam.first).arg(missingParam.second);
398 if (params.isEmpty()) {
399 params += param;
400 } else {
401 params += QStringLiteral(", %1").arg(param);
402 }
403
404 }
405 _missingParams.clear();
406
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));
408}
409
410QObject *QGCApplication::_rootQmlObject()
411{
412 if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) {
413 return _qmlAppEngine->rootObjects()[0];
414 }
415
416 return nullptr;
417}
418
420{
421 // PreArm messages are handled by Vehicle and shown in Map
422 if (message.startsWith(QStringLiteral("PreArm")) || message.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive)) {
423 return;
424 }
425
426 QObject *const rootQmlObject = _rootQmlObject();
427 if (rootQmlObject && _showErrorsInToolbar) {
428 QVariant varReturn;
429 QVariant varMessage = QVariant::fromValue(message);
430 QMetaObject::invokeMethod(rootQmlObject, "showCriticalVehicleMessage", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, varMessage));
431 } else if (runningUnitTests() || !_showErrorsInToolbar) {
432 // Unit tests can run without UI
433 qCDebug(QGCApplicationLog) << "QGCApplication::showCriticalVehicleMessage unittest" << message;
434 } else {
435 qCWarning(QGCApplicationLog) << "Internal error";
436 }
437}
438
439void QGCApplication::showAppMessage(const QString &message, const QString &title)
440{
441 const QString dialogTitle = title.isEmpty() ? applicationName() : title;
442
443 QObject *const rootQmlObject = _rootQmlObject();
444 if (rootQmlObject) {
445 QVariant varReturn;
446 QVariant varMessage = QVariant::fromValue(message);
447 QMetaObject::invokeMethod(rootQmlObject, "_showMessageDialog", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, dialogTitle), Q_ARG(QVariant, varMessage));
448 } else if (runningUnitTests()) {
449 // Unit tests can run without UI
450 // We don't use a logging category to make it easier to debug unit tests
451 qDebug() << "QGCApplication::showAppMessage unittest title:message" << dialogTitle << message;
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
459void QGCApplication::showRebootAppMessage(const QString &message, const QString &title)
460{
461 static QTime lastRebootMessage;
462
463 const QTime currentTime = QTime::currentTime();
464 const QTime previousTime = lastRebootMessage;
465 lastRebootMessage = currentTime;
466
467 if (previousTime.isValid() && (previousTime.msecsTo(currentTime) < (60 * 1000 * 2))) {
468 // Debounce reboot messages
469 return;
470 }
471
472 showAppMessage(message, title);
473}
474
475void QGCApplication::_showDelayedAppMessages()
476{
477 if (_rootQmlObject()) {
478 for (const QPair<QString, QString>& appMsg: _delayedAppMessages) {
479 showAppMessage(appMsg.second, appMsg.first);
480 }
481 _delayedAppMessages.clear();
482 } else {
483 QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages);
484 }
485}
486
488{
489 if (!_mainRootWindow) {
490 _mainRootWindow = qobject_cast<QQuickWindow*>(_rootQmlObject());
491 }
492
493 return _mainRootWindow;
494}
495
497{
498 if (_rootQmlObject()) {
499 QMetaObject::invokeMethod(_rootQmlObject(), "showVehicleConfig");
500 }
501}
502
504{
505 if (_rootQmlObject()) {
506 QMetaObject::invokeMethod(_rootQmlObject(), "attemptWindowClose");
507 }
508}
509
510void QGCApplication::_checkForNewVersion()
511{
512 if (_runningUnitTests) {
513 return;
514 }
515
516 if (!_parseVersionText(applicationVersion(), _majorVersion, _minorVersion, _buildVersion)) {
517 return;
518 }
519
520 const QString versionCheckFile = QGCCorePlugin::instance()->stableVersionCheckFileUrl();
521 if (!versionCheckFile.isEmpty()) {
522 QGCFileDownload *const download = new QGCFileDownload(this);
523 (void) connect(download, &QGCFileDownload::finished, this, &QGCApplication::_qgcCurrentStableVersionDownloadComplete);
524 if (!download->start(versionCheckFile)) {
525 qCDebug(QGCApplicationLog) << "Download QGC stable version failed to start" << download->errorString();
526 download->deleteLater();
527 }
528 }
529}
530
531void QGCApplication::_qgcCurrentStableVersionDownloadComplete(bool success, const QString &localFile, const QString &errorMsg)
532{
533 if (success) {
534 QFile versionFile(localFile);
535 if (versionFile.open(QIODevice::ReadOnly)) {
536 QTextStream textStream(&versionFile);
537 const QString version = textStream.readLine();
538
539 qCDebug(QGCApplicationLog) << version;
540
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"));
547 }
548 }
549 }
550 } else if (!errorMsg.isEmpty()) {
551 qCDebug(QGCApplicationLog) << "Download QGC stable version failed" << errorMsg;
552 }
553
554 sender()->deleteLater();
555}
556
557bool QGCApplication::_parseVersionText(const QString &versionString, int &majorVersion, int &minorVersion, int &buildVersion)
558{
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();
565 return true;
566 }
567
568 return false;
569}
570
572{
573 QSettings settings;
574 const QDir parameterDir = QFileInfo(settings.fileName()).dir();
575 return parameterDir.filePath(QStringLiteral("ParameterFactMetaData.json"));
576}
577
579{
580 QSettings settings;
581 const QDir airframeDir = QFileInfo(settings.fileName()).dir();
582 return airframeDir.filePath(QStringLiteral("PX4AirframeFactMetaData.xml"));
583}
584
585int QGCApplication::CompressedSignalList::_signalIndex(const QMetaMethod &method)
586{
587 if (method.methodType() != QMetaMethod::Signal) {
588 qCWarning(QGCApplicationLog) << "Internal error:" << Q_FUNC_INFO << "not a signal" << method.methodType();
589 return -1;
590 }
591
592 int index = -1;
593 const QMetaObject *metaObject = method.enclosingMetaObject();
594 for (int i=0; i<=method.methodIndex(); i++) {
595 if (metaObject->method(i).methodType() != QMetaMethod::Signal) {
596 continue;
597 }
598 index++;
599 }
600
601 return index;
602}
603
604void QGCApplication::CompressedSignalList::add(const QMetaMethod &method)
605{
606 const QMetaObject *metaObject = method.enclosingMetaObject();
607 const int signalIndex = _signalIndex(method);
608
609 if (signalIndex != -1 && !contains(metaObject, signalIndex)) {
610 _signalMap[method.enclosingMetaObject()].insert(signalIndex);
611 }
612}
613
614void QGCApplication::CompressedSignalList::remove(const QMetaMethod &method)
615{
616 const int signalIndex = _signalIndex(method);
617 const QMetaObject *const metaObject = method.enclosingMetaObject();
618
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);
623 }
624 }
625}
626
627bool QGCApplication::CompressedSignalList::contains(const QMetaObject *metaObject, int signalIndex)
628{
629 return _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex);
630}
631
632void QGCApplication::addCompressedSignal(const QMetaMethod &method)
633{
634 _compressedSignals.add(method);
635}
636
637void QGCApplication::removeCompressedSignal(const QMetaMethod &method)
638{
639 _compressedSignals.remove(method);
640}
641
642QT_WARNING_PUSH
643QT_WARNING_DISABLE_DEPRECATED
644bool QGCApplication::compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents)
645{
646 if (event->type() != QEvent::MetaCall) {
647 return QGuiApplication::compressEvent(event, receiver, postedEvents);
648 }
649
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);
653 }
654
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()) {
658 continue;
659 }
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()) {
662 continue;
663 }
664 /* Keep The Newest Call */
665 // We can't merely qSwap the existing posted event with the new one, since QEvent
666 // keeps track of whether it has been posted. Deletion of a formerly posted event
667 // takes the posted event list mutex and does a useless search of the posted event
668 // list upon deletion. We thus clear the QEvent::posted flag before deletion.
669 struct EventHelper : private QEvent {
670 static void clearPostedFlag(QEvent * ev) {
671 (&static_cast<EventHelper*>(ev)->t)[1] &= ~0x8001; // Hack to clear QEvent::posted
672 }
673 };
674 EventHelper::clearPostedFlag(cur.event);
675 delete cur.event;
676 cur.event = event;
677 return true;
678 }
679
680 return false;
681}
682QT_WARNING_POP
683
685{
686 if (e->type() == QEvent::Quit) {
687 if (!_mainRootWindow) {
688 return QGuiApplication::event(e);
689 }
690 // On OSX if the user selects Quit from the menu (or Command-Q) the ApplicationWindow does not signal closing. Instead you get a Quit event here only.
691 // This in turn causes the standard QGC shutdown sequence to not run. So in this case we close the window ourselves such that the
692 // signal is sent and the normal shutdown sequence runs.
693 const bool forceClose = _mainRootWindow->property("_forceClose").toBool();
694 qCDebug(QGCApplicationLog) << "Quit event" << forceClose;
695 // forceClose
696 // true: Standard QGC shutdown sequence is complete. Let the app quit normally by falling through to the base class processing.
697 // false: QGC shutdown sequence has not been run yet. Don't let this event close the app yet. Close the main window to kick off the normal shutdown.
698 if (!forceClose) {
699 //
700 _mainRootWindow->close();
701 e->ignore();
702 return true;
703 }
704 }
705
706 return QGuiApplication::event(e);
707}
708
710{
711 return dynamic_cast<QGCImageProvider*>(_qmlAppEngine->imageProvider(_qgcImageProviderId));
712}
713
715{
716 qCDebug(QGCApplicationLog) << "Exit";
717
718 if (_videoManagerInitialized) {
720 }
721
723
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;
730 } else {
731 qCWarning(QGCApplicationLog) << "Failed to remove test run settings file:" << settingsFile;
732 }
733 }
734
735 // Remove the app-specific settings directory (parent of ParamCache)
736 QDir settingsAppDir(ParameterManager::parameterCacheDir());
737 settingsAppDir.cdUp();
738 if (settingsAppDir.exists()) {
739 if (settingsAppDir.removeRecursively()) {
740 qCDebug(QGCApplicationLog) << "Removed test run settings directory:" << settingsAppDir.absolutePath();
741 } else {
742 qCWarning(QGCApplicationLog) << "Failed to remove test run settings directory:" << settingsAppDir.absolutePath();
743 }
744 }
745
746 QDir appDir(SettingsManager::instance()->appSettings()->savePath()->rawValue().toString());
747 if (appDir.exists()) {
748 if (appDir.removeRecursively()) {
749 qCDebug(QGCApplicationLog) << "Removed test run app data directory:" << appDir.absolutePath();
750 } else {
751 qCWarning(QGCApplicationLog) << "Failed to remove test run app data directory:" << appDir.absolutePath();
752 }
753 }
754 }
755
756 // This is bad, but currently qobject inheritances are incorrect and cause crashes on exit without
757 delete _qmlAppEngine;
758}
Unified file download utility with decompression, verification, and QML support.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void savePathsChanged()
static constexpr const char * clearSettingsNextBootKey
Definition AppSettings.h:68
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()
Definition FollowMe.cc:35
void init()
Definition FollowMe.cc:40
static JoystickManager * instance()
void loadLinkConfigurationList()
void startAutoConnectedLinks()
static LinkManager * instance()
LogRemoteSink * remoteSink()
Definition LogManager.h:63
static LogManager * instance()
Definition LogManager.cc:66
void setHost(const QString &host)
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
virtual void init()
virtual void cleanup()
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.