/**************************************************************************** * * (c) 2009-2020 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ /** * @file * @brief Implementation of class QGCApplication * * @author Lorenz Meier * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef QGC_ENABLE_BLUETOOTH #include #endif #include #if defined(QGC_GST_STREAMING) #include "GStreamer.h" #endif #include "QGC.h" #include "QGCApplication.h" #include "CmdLineOptParser.h" #include "UDPLink.h" #include "LinkManager.h" #include "UASMessageHandler.h" #include "QGCTemporaryFile.h" #include "QGCPalette.h" #include "QGCMapPalette.h" #include "QGCLoggingCategory.h" #include "ParameterEditorController.h" #include "ESP8266ComponentController.h" #include "ScreenToolsController.h" #include "QGCFileDialogController.h" #include "RCChannelMonitorController.h" #include "SyslinkComponentController.h" #include "AutoPilotPlugin.h" #include "VehicleComponent.h" #include "FirmwarePluginManager.h" #include "MultiVehicleManager.h" #include "Vehicle.h" #include "JoystickConfigController.h" #include "JoystickManager.h" #include "QmlObjectListModel.h" #include "QGCGeoBoundingCube.h" #include "MissionManager.h" #include "QGroundControlQmlGlobal.h" #include "FlightMapSettings.h" #include "FlightPathSegment.h" #include "PlanMasterController.h" #include "VideoManager.h" #include "VideoReceiver.h" #include "LogDownloadController.h" #if defined(QGC_ENABLE_MAVLINK_INSPECTOR) #include "MAVLinkInspectorController.h" #endif #include "HorizontalFactValueGrid.h" #include "VerticalFactValueGrid.h" #include "InstrumentValueData.h" #include "AppMessages.h" #include "SimulatedPosition.h" #include "PositionManager.h" #include "FollowMe.h" #include "MissionCommandTree.h" #include "QGCMapPolygon.h" #include "QGCMapCircle.h" #include "ParameterManager.h" #include "SettingsManager.h" #include "QGCCorePlugin.h" #include "QGCCameraManager.h" #include "CameraCalc.h" #include "VisualMissionItem.h" #include "EditPositionDialogController.h" #include "FactValueSliderListModel.h" #include "ShapeFileHelper.h" #include "QGCFileDownload.h" #include "FirmwareImage.h" #include "MavlinkConsoleController.h" #include "GeoTagController.h" #include "LogReplayLink.h" #include "VehicleObjectAvoidance.h" #include "TrajectoryPoints.h" #include "RCToParamDialogController.h" #include "QGCImageProvider.h" #include "TerrainProfile.h" #include "ToolStripAction.h" #include "ToolStripActionList.h" #if defined(QGC_ENABLE_PAIRING) #include "PairingManager.h" #endif #ifndef __mobile__ #include "FirmwareUpgradeController.h" #endif #ifndef NO_SERIAL_LINK #include "SerialLink.h" #endif #ifndef __mobile__ #include "GPS/GPSManager.h" #endif #ifdef QGC_RTLAB_ENABLED #include "OpalLink.h" #endif #ifdef Q_OS_LINUX #ifndef __mobile__ #include #include #endif #endif #include "QGCMapEngine.h" class FinishVideoInitialization : public QRunnable { public: FinishVideoInitialization(VideoManager* manager) : _manager(manager) {} void run () { _manager->_initVideo(); } private: VideoManager* _manager; }; QGCApplication* QGCApplication::_app = nullptr; const char* QGCApplication::_deleteAllSettingsKey = "DeleteAllSettingsNextBoot"; const char* QGCApplication::_settingsVersionKey = "SettingsVersion"; // Mavlink status structures for entire app mavlink_status_t m_mavlink_status[MAVLINK_COMM_NUM_BUFFERS]; // Qml Singleton factories static QObject* screenToolsControllerSingletonFactory(QQmlEngine*, QJSEngine*) { ScreenToolsController* screenToolsController = new ScreenToolsController; return screenToolsController; } static QObject* qgroundcontrolQmlGlobalSingletonFactory(QQmlEngine*, QJSEngine*) { // We create this object as a QGCTool even though it isn't in the toolbox QGroundControlQmlGlobal* qmlGlobal = new QGroundControlQmlGlobal(qgcApp(), qgcApp()->toolbox()); qmlGlobal->setToolbox(qgcApp()->toolbox()); return qmlGlobal; } static QObject* shapeFileHelperSingletonFactory(QQmlEngine*, QJSEngine*) { return new ShapeFileHelper; } QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting) : QApplication (argc, argv) , _runningUnitTests (unitTesting) { _app = this; _msecsElapsedTime.start(); #ifdef Q_OS_LINUX #ifndef __mobile__ if (!_runningUnitTests) { if (getuid() == 0) { _exitWithError(QString( tr("You are running %1 as root. " "You should not do this since it will cause other issues with %1." "%1 will now exit.

" "If you are having serial port issues on Ubuntu, execute the following commands to fix most issues:
" "
sudo usermod -a -G dialout $USER
" "sudo apt-get remove modemmanager
").arg(qgcApp()->applicationName()))); return; } // Determine if we have the correct permissions to access USB serial devices QFile permFile("/etc/group"); if(permFile.open(QIODevice::ReadOnly)) { while(!permFile.atEnd()) { QString line = permFile.readLine(); if (line.contains("dialout") && !line.contains(getenv("USER"))) { permFile.close(); _exitWithError(QString( tr("The current user does not have the correct permissions to access serial devices. " "You should also remove modemmanager since it also interferes.

" "If you are using Ubuntu, execute the following commands to fix these issues:
" "
sudo usermod -a -G dialout $USER
" "sudo apt-get remove modemmanager
"))); return; } } permFile.close(); } // Always set style to default, this way QT_QUICK_CONTROLS_STYLE environment variable doesn't cause random changes in ui QQuickStyle::setStyle("Default"); } #endif #endif // Setup for network proxy support QNetworkProxyFactory::setUseSystemConfiguration(true); // Parse command line options bool fClearSettingsOptions = false; // Clear stored settings bool fClearCache = false; // Clear parameter/airframe caches bool logging = false; // Turn on logging QString loggingOptions; CmdLineOpt_t rgCmdLineOptions[] = { { "--clear-settings", &fClearSettingsOptions, nullptr }, { "--clear-cache", &fClearCache, nullptr }, { "--logging", &logging, &loggingOptions }, { "--fake-mobile", &_fakeMobile, nullptr }, { "--log-output", &_logOutput, nullptr }, // Add additional command line option flags here }; ParseCmdLineOptions(argc, argv, rgCmdLineOptions, sizeof(rgCmdLineOptions)/sizeof(rgCmdLineOptions[0]), false); // Set up timer for delayed missing fact display _missingParamsDelayedDisplayTimer.setSingleShot(true); _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout); connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, &QGCApplication::_missingParamsDisplay); // Set application information QString applicationName; if (_runningUnitTests) { // We don't want unit tests to use the same QSettings space as the normal app. So we tweak the app // name. Also we want to run unit tests with clean settings every time. applicationName = QStringLiteral("%1_unittest").arg(QGC_APPLICATION_NAME); } else { #ifdef DAILY_BUILD // This gives daily builds their own separate settings space. Allowing you to use daily and stable builds // side by side without daily screwing up your stable settings. applicationName = QStringLiteral("%1 Daily").arg(QGC_APPLICATION_NAME); #else applicationName = QGC_APPLICATION_NAME; #endif } setApplicationName(applicationName); setOrganizationName(QGC_ORG_NAME); setOrganizationDomain(QGC_ORG_DOMAIN); this->setApplicationVersion(QString(GIT_VERSION)); // Set settings format QSettings::setDefaultFormat(QSettings::IniFormat); QSettings settings; qDebug() << "Settings location" << settings.fileName() << "Is writable?:" << settings.isWritable(); #ifdef UNITTEST_BUILD if (!settings.isWritable()) { qWarning() << "Setings location is not writable"; } #endif // The setting will delete all settings on this boot fClearSettingsOptions |= settings.contains(_deleteAllSettingsKey); if (_runningUnitTests) { // Unit tests run with clean settings fClearSettingsOptions = true; } if (fClearSettingsOptions) { // User requested settings to be cleared on command line settings.clear(); // Clear parameter cache QDir paramDir(ParameterManager::parameterCacheDir()); paramDir.removeRecursively(); paramDir.mkpath(paramDir.absolutePath()); } else { // Determine if upgrade message for settings version bump is required. Check and clear must happen before toolbox is started since // that will write some settings. if (settings.contains(_settingsVersionKey)) { if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) { settings.clear(); _settingsUpgraded = true; } } } settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION); if (fClearCache) { QDir dir(ParameterManager::parameterCacheDir()); dir.removeRecursively(); QFile airframe(cachedAirframeMetaDataFile()); airframe.remove(); QFile parameter(cachedParameterMetaDataFile()); parameter.remove(); } // Set up our logging filters QGCLoggingCategoryRegister::instance()->setFilterRulesFromSettings(loggingOptions); // Initialize Bluetooth #ifdef QGC_ENABLE_BLUETOOTH QBluetoothLocalDevice localDevice; if (localDevice.isValid()) { _bluetoothAvailable = true; } #endif // Gstreamer debug settings int gstDebugLevel = 0; if (settings.contains(AppSettings::gstDebugLevelName)) { gstDebugLevel = settings.value(AppSettings::gstDebugLevelName).toInt(); } #if defined(QGC_GST_STREAMING) // Initialize Video Receiver GStreamer::initialize(argc, argv, gstDebugLevel); #else Q_UNUSED(gstDebugLevel) #endif // We need to set language as early as possible prior to loading on JSON files. setLanguage(); _toolbox = new QGCToolbox(this); _toolbox->setChildToolboxes(); #ifndef __mobile__ _gpsRtkFactGroup = new GPSRTKFactGroup(this); GPSManager *gpsManager = _toolbox->gpsManager(); if (gpsManager) { connect(gpsManager, &GPSManager::onConnect, this, &QGCApplication::_onGPSConnect); connect(gpsManager, &GPSManager::onDisconnect, this, &QGCApplication::_onGPSDisconnect); connect(gpsManager, &GPSManager::surveyInStatus, this, &QGCApplication::_gpsSurveyInStatus); connect(gpsManager, &GPSManager::satelliteUpdate, this, &QGCApplication::_gpsNumSatellites); } #endif /* __mobile__ */ _checkForNewVersion(); } void QGCApplication::_exitWithError(QString errorMessage) { _error = true; QQmlApplicationEngine* pEngine = new QQmlApplicationEngine(this); pEngine->addImportPath("qrc:/qml"); pEngine->rootContext()->setContextProperty("errorMessage", errorMessage); pEngine->load(QUrl(QStringLiteral("qrc:/qml/ExitWithErrorWindow.qml"))); // Exit main application when last window is closed connect(this, &QGCApplication::lastWindowClosed, this, QGCApplication::quit); } void QGCApplication::setLanguage() { _locale = QLocale::system(); qDebug() << "System reported locale:" << _locale << "; Name" << _locale.name() << "; Preffered (used in maps): " << (QLocale::system().uiLanguages().length() > 0 ? QLocale::system().uiLanguages()[0] : "None"); int langID = AppSettings::_languageID(); //-- See App.SettinsGroup.json for index if(langID) { switch(langID) { case 1: _locale = QLocale(QLocale::Bulgarian); break; case 2: _locale = QLocale(QLocale::Chinese); break; case 3: _locale = QLocale(QLocale::Dutch); break; case 4: _locale = QLocale(QLocale::English); break; case 5: _locale = QLocale(QLocale::Finnish); break; case 6: _locale = QLocale(QLocale::French); break; case 7: _locale = QLocale(QLocale::German); break; case 8: _locale = QLocale(QLocale::Greek); break; case 9: _locale = QLocale(QLocale::Hebrew); break; case 10: _locale = QLocale(QLocale::Italian); break; case 11: _locale = QLocale(QLocale::Japanese); break; case 12: _locale = QLocale(QLocale::Korean); break; case 13: _locale = QLocale(QLocale::Norwegian); break; case 14: _locale = QLocale(QLocale::Polish); break; case 15: _locale = QLocale(QLocale::Portuguese); break; case 16: _locale = QLocale(QLocale::Russian); break; case 17: _locale = QLocale(QLocale::Spanish); break; case 18: _locale = QLocale(QLocale::Swedish); break; case 19: _locale = QLocale(QLocale::Turkish); break; } } //-- We have specific fonts for Korean if(_locale == QLocale::Korean) { qCDebug(LocalizationLog) << "Loading Korean fonts" << _locale.name(); if(QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Regular") < 0) { qCWarning(LocalizationLog) << "Could not load /fonts/NanumGothic-Regular font"; } if(QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Bold") < 0) { qCWarning(LocalizationLog) << "Could not load /fonts/NanumGothic-Bold font"; } } qCDebug(LocalizationLog) << "Loading localizations for" << _locale.name(); _app->removeTranslator(&_qgcTranslatorJSON); _app->removeTranslator(&_qgcTranslatorSourceCode); _app->removeTranslator(&_qgcTranslatorQtLibs); if (_locale.name() != "en_US") { QLocale::setDefault(_locale); if(_qgcTranslatorQtLibs.load("qt_" + _locale.name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { _app->installTranslator(&_qgcTranslatorQtLibs); } else { qCWarning(LocalizationLog) << "Qt lib localization for" << _locale.name() << "is not present"; } if(_qgcTranslatorSourceCode.load(_locale, QLatin1String("qgc_source_"), "", ":/i18n")) { _app->installTranslator(&_qgcTranslatorSourceCode); } else { qCWarning(LocalizationLog) << "Error loading source localization for" << _locale.name(); } if(_qgcTranslatorJSON.load(_locale, QLatin1String("qgc_json_"), "", ":/i18n")) { _app->installTranslator(&_qgcTranslatorJSON); } else { qCWarning(LocalizationLog) << "Error loading json localization for" << _locale.name(); } } if(_qmlAppEngine) _qmlAppEngine->retranslate(); emit languageChanged(_locale); } void QGCApplication::_shutdown() { // Close out all Qml before we delete toolbox. This way we don't get all sorts of null reference complaints from Qml. delete _qmlAppEngine; delete _toolbox; } QGCApplication::~QGCApplication() { // Place shutdown code in _shutdown _app = nullptr; } void QGCApplication::_initCommon() { static const char* kRefOnly = "Reference only"; static const char* kQGroundControl = "QGroundControl"; static const char* kQGCControllers = "QGroundControl.Controllers"; static const char* kQGCVehicle = "QGroundControl.Vehicle"; static const char* kQGCTemplates = "QGroundControl.Templates"; QSettings settings; // Register our Qml objects qmlRegisterType ("QGroundControl.Palette", 1, 0, "QGCPalette"); qmlRegisterType ("QGroundControl.Palette", 1, 0, "QGCMapPalette"); qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "Vehicle", kRefOnly); qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "MissionManager", kRefOnly); qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "ParameterManager", kRefOnly); qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "VehicleObjectAvoidance", kRefOnly); qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "QGCCameraManager", kRefOnly); qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "QGCCameraControl", kRefOnly); qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "QGCVideoStreamInfo", kRefOnly); qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "LinkInterface", kRefOnly); qmlRegisterUncreatableType (kQGCControllers, 1, 0, "MissionController", kRefOnly); qmlRegisterUncreatableType (kQGCControllers, 1, 0, "GeoFenceController", kRefOnly); qmlRegisterUncreatableType (kQGCControllers, 1, 0, "RallyPointController", kRefOnly); qmlRegisterUncreatableType (kQGroundControl, 1, 0, "MissionItem", kRefOnly); qmlRegisterUncreatableType (kQGroundControl, 1, 0, "VisualMissionItem", kRefOnly); qmlRegisterUncreatableType (kQGroundControl, 1, 0, "FlightPathSegment", kRefOnly); qmlRegisterUncreatableType (kQGroundControl, 1, 0, "QmlObjectListModel", kRefOnly); qmlRegisterUncreatableType (kQGroundControl, 1, 0, "MissionCommandTree", kRefOnly); qmlRegisterUncreatableType (kQGroundControl, 1, 0, "CameraCalc", kRefOnly); qmlRegisterUncreatableType (kQGroundControl, 1, 0, "LogReplayLink", kRefOnly); qmlRegisterUncreatableType (kQGroundControl, 1, 0, "InstrumentValueData", kRefOnly); qmlRegisterType (kQGroundControl, 1, 0, "LogReplayLinkController"); #if defined(QGC_ENABLE_MAVLINK_INSPECTOR) qmlRegisterUncreatableType (kQGroundControl, 1, 0, "MAVLinkChart", kRefOnly); #endif #if defined(QGC_ENABLE_PAIRING) qmlRegisterUncreatableType (kQGroundControl, 1, 0, "PairingManager", kRefOnly); #endif qmlRegisterUncreatableType ("QGroundControl.AutoPilotPlugin", 1, 0, "AutoPilotPlugin", kRefOnly); qmlRegisterUncreatableType ("QGroundControl.AutoPilotPlugin", 1, 0, "VehicleComponent", kRefOnly); qmlRegisterUncreatableType ("QGroundControl.JoystickManager", 1, 0, "JoystickManager", kRefOnly); qmlRegisterUncreatableType ("QGroundControl.JoystickManager", 1, 0, "Joystick", kRefOnly); qmlRegisterUncreatableType ("QGroundControl.QGCPositionManager", 1, 0, "QGCPositionManager", kRefOnly); qmlRegisterUncreatableType("QGroundControl.FactControls", 1, 0, "FactValueSliderListModel", kRefOnly); qmlRegisterUncreatableType ("QGroundControl.FlightMap", 1, 0, "QGCMapPolygon", kRefOnly); qmlRegisterUncreatableType ("QGroundControl.FlightMap", 1, 0, "QGCGeoBoundingCube", kRefOnly); qmlRegisterUncreatableType ("QGroundControl.FlightMap", 1, 0, "TrajectoryPoints", kRefOnly); qmlRegisterUncreatableType (kQGCTemplates, 1, 0, "FactValueGrid", kRefOnly); qmlRegisterType (kQGCTemplates, 1, 0, "HorizontalFactValueGrid"); qmlRegisterType (kQGCTemplates, 1, 0, "VerticalFactValueGrid"); qmlRegisterType ("QGroundControl.FlightMap", 1, 0, "QGCMapCircle"); qmlRegisterType (kQGCControllers, 1, 0, "ParameterEditorController"); qmlRegisterType (kQGCControllers, 1, 0, "ESP8266ComponentController"); qmlRegisterType (kQGCControllers, 1, 0, "ScreenToolsController"); qmlRegisterType (kQGCControllers, 1, 0, "PlanMasterController"); qmlRegisterType (kQGCControllers, 1, 0, "QGCFileDialogController"); qmlRegisterType (kQGCControllers, 1, 0, "RCChannelMonitorController"); qmlRegisterType (kQGCControllers, 1, 0, "JoystickConfigController"); qmlRegisterType (kQGCControllers, 1, 0, "LogDownloadController"); qmlRegisterType (kQGCControllers, 1, 0, "SyslinkComponentController"); qmlRegisterType (kQGCControllers, 1, 0, "EditPositionDialogController"); qmlRegisterType (kQGCControllers, 1, 0, "RCToParamDialogController"); qmlRegisterType ("QGroundControl.Controls", 1, 0, "TerrainProfile"); qmlRegisterType ("QGroundControl.Controls", 1, 0, "ToolStripAction"); qmlRegisterType ("QGroundControl.Controls", 1, 0, "ToolStripActionList"); #ifndef __mobile__ #ifndef NO_SERIAL_LINK qmlRegisterType (kQGCControllers, 1, 0, "FirmwareUpgradeController"); #endif #endif qmlRegisterType (kQGCControllers, 1, 0, "GeoTagController"); qmlRegisterType (kQGCControllers, 1, 0, "MavlinkConsoleController"); #if defined(QGC_ENABLE_MAVLINK_INSPECTOR) qmlRegisterType (kQGCControllers, 1, 0, "MAVLinkInspectorController"); #endif // Register Qml Singletons qmlRegisterSingletonType ("QGroundControl", 1, 0, "QGroundControl", qgroundcontrolQmlGlobalSingletonFactory); qmlRegisterSingletonType ("QGroundControl.ScreenToolsController", 1, 0, "ScreenToolsController", screenToolsControllerSingletonFactory); qmlRegisterSingletonType ("QGroundControl.ShapeFileHelper", 1, 0, "ShapeFileHelper", shapeFileHelperSingletonFactory); // Although this should really be in _initForNormalAppBoot putting it here allowws us to create unit tests which pop up more easily if(QFontDatabase::addApplicationFont(":/fonts/opensans") < 0) { qWarning() << "Could not load /fonts/opensans font"; } if(QFontDatabase::addApplicationFont(":/fonts/opensans-demibold") < 0) { qWarning() << "Could not load /fonts/opensans-demibold font"; } } bool QGCApplication::_initForNormalAppBoot() { QSettings settings; _qmlAppEngine = toolbox()->corePlugin()->createQmlApplicationEngine(this); toolbox()->corePlugin()->createRootWindow(_qmlAppEngine); // Image provider for PX4 Flow QQuickImageProvider* pImgProvider = dynamic_cast(qgcApp()->toolbox()->imageProvider()); _qmlAppEngine->addImageProvider(QStringLiteral("QGCImages"), pImgProvider); QQuickWindow* rootWindow = (QQuickWindow*)qgcApp()->mainRootWindow(); if (rootWindow) { rootWindow->scheduleRenderJob (new FinishVideoInitialization (toolbox()->videoManager()), QQuickWindow::BeforeSynchronizingStage); } // Safe to show popup error messages now that main window is created UASMessageHandler* msgHandler = qgcApp()->toolbox()->uasMessageHandler(); if (msgHandler) { msgHandler->showErrorsInToolbar(); } // Now that main window is up check for lost log files connect(this, &QGCApplication::checkForLostLogFiles, toolbox()->mavlinkProtocol(), &MAVLinkProtocol::checkForLostLogFiles); emit checkForLostLogFiles(); // Load known link configurations toolbox()->linkManager()->loadLinkConfigurationList(); // Probe for joysticks toolbox()->joystickManager()->init(); if (_settingsUpgraded) { showAppMessage(QString(tr("The format for %1 saved settings has been modified. " "Your saved settings have been reset to defaults.")).arg(applicationName())); } // Connect links with flag AutoconnectLink toolbox()->linkManager()->startAutoConnectedLinks(); if (getQGCMapEngine()->wasCacheReset()) { showAppMessage(tr("The Offline Map Cache database has been upgraded. " "Your old map cache sets have been reset.")); } settings.sync(); return true; } bool QGCApplication::_initForUnitTests() { return true; } void QGCApplication::deleteAllSettingsNextBoot(void) { QSettings settings; settings.setValue(_deleteAllSettingsKey, true); } void QGCApplication::clearDeleteAllSettingsNextBoot(void) { QSettings settings; settings.remove(_deleteAllSettingsKey); } /// @brief Returns the QGCApplication object singleton. QGCApplication* qgcApp(void) { return QGCApplication::_app; } void QGCApplication::informationMessageBoxOnMainThread(const QString& /*title*/, const QString& msg) { showAppMessage(msg); } void QGCApplication::warningMessageBoxOnMainThread(const QString& /*title*/, const QString& msg) { showAppMessage(msg); } void QGCApplication::criticalMessageBoxOnMainThread(const QString& /*title*/, const QString& msg) { showAppMessage(msg); } void QGCApplication::saveTelemetryLogOnMainThread(QString tempLogfile) { // The vehicle is gone now and we are shutting down so we need to use a message box for errors to hold shutdown and show the error if (_checkTelemetrySavePath(true /* useMessageBox */)) { QString saveDirPath = _toolbox->settingsManager()->appSettings()->telemetrySavePath(); QDir saveDir(saveDirPath); QString nameFormat("%1%2.%3"); QString dtFormat("yyyy-MM-dd hh-mm-ss"); int tryIndex = 1; QString saveFileName = nameFormat.arg( QDateTime::currentDateTime().toString(dtFormat)).arg(QStringLiteral("")).arg(toolbox()->settingsManager()->appSettings()->telemetryFileExtension); while (saveDir.exists(saveFileName)) { saveFileName = nameFormat.arg( QDateTime::currentDateTime().toString(dtFormat)).arg(QStringLiteral(".%1").arg(tryIndex++)).arg(toolbox()->settingsManager()->appSettings()->telemetryFileExtension); } QString saveFilePath = saveDir.absoluteFilePath(saveFileName); QFile tempFile(tempLogfile); if (!tempFile.copy(saveFilePath)) { QString error = tr("Unable to save telemetry log. Error copying telemetry to '%1': '%2'.").arg(saveFilePath).arg(tempFile.errorString()); showAppMessage(error); } } QFile::remove(tempLogfile); } void QGCApplication::checkTelemetrySavePathOnMainThread() { // This is called with an active vehicle so don't pop message boxes which holds ui thread _checkTelemetrySavePath(false /* useMessageBox */); } bool QGCApplication::_checkTelemetrySavePath(bool /*useMessageBox*/) { QString saveDirPath = _toolbox->settingsManager()->appSettings()->telemetrySavePath(); if (saveDirPath.isEmpty()) { QString error = tr("Unable to save telemetry log. Application save directory is not set."); showAppMessage(error); return false; } QDir saveDir(saveDirPath); if (!saveDir.exists()) { QString error = tr("Unable to save telemetry log. Telemetry save directory \"%1\" does not exist.").arg(saveDirPath); showAppMessage(error); return false; } return true; } void QGCApplication::reportMissingParameter(int componentId, const QString& name) { QPair missingParam(componentId, name); if (!_missingParams.contains(missingParam)) { _missingParams.append(missingParam); } _missingParamsDelayedDisplayTimer.start(); } /// Called when the delay timer fires to show the missing parameters warning void QGCApplication::_missingParamsDisplay(void) { if (_missingParams.count()) { QString params; for (QPair& missingParam: _missingParams) { QString param = QStringLiteral("%1:%2").arg(missingParam.first).arg(missingParam.second); if (params.isEmpty()) { params += param; } else { params += QStringLiteral(", %1").arg(param); } } _missingParams.clear(); 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)); } } QObject* QGCApplication::_rootQmlObject() { if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) return _qmlAppEngine->rootObjects()[0]; return nullptr; } void QGCApplication::showVehicleMessage(const QString& message) { // PreArm messages are handled by Vehicle and shown in Map if (message.startsWith(QStringLiteral("PreArm")) || message.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive)) { return; } QObject* rootQmlObject = _rootQmlObject(); if (rootQmlObject) { QVariant varReturn; QVariant varMessage = QVariant::fromValue(message); QMetaObject::invokeMethod(_rootQmlObject(), "showVehicleMessage", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, varMessage)); } else if (runningUnitTests()) { // Unit tests can run without UI qDebug() << "QGCApplication::showVehicleMessage unittest" << message; } else { qWarning() << "Internal error"; } } void QGCApplication::showAppMessage(const QString& message, const QString& title) { QString dialogTitle = title.isEmpty() ? applicationName() : title; QObject* rootQmlObject = _rootQmlObject(); if (rootQmlObject) { QVariant varReturn; QVariant varMessage = QVariant::fromValue(message); QMetaObject::invokeMethod(_rootQmlObject(), "showMessageDialog", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, dialogTitle), Q_ARG(QVariant, varMessage)); } else if (runningUnitTests()) { // Unit tests can run without UI qDebug() << "QGCApplication::showAppMessage unittest" << message << dialogTitle; } else { // UI isn't ready yet _delayedAppMessages.append(QPair(dialogTitle, message)); QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages); } } void QGCApplication::_showDelayedAppMessages(void) { if (_rootQmlObject()) { for (const QPair& appMsg: _delayedAppMessages) { showAppMessage(appMsg.second, appMsg.first); } _delayedAppMessages.clear(); } else { QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages); } } QQuickItem* QGCApplication::mainRootWindow() { if(!_mainRootWindow) { _mainRootWindow = reinterpret_cast(_rootQmlObject()); } return _mainRootWindow; } void QGCApplication::showSetupView() { if(_rootQmlObject()) { QMetaObject::invokeMethod(_rootQmlObject(), "showSetupView"); } } void QGCApplication::qmlAttemptWindowClose() { if(_rootQmlObject()) { QMetaObject::invokeMethod(_rootQmlObject(), "attemptWindowClose"); } } bool QGCApplication::isInternetAvailable() { if(_toolbox->settingsManager()->appSettings()->checkInternet()->rawValue().toBool()) return getQGCMapEngine()->isInternetActive(); return true; } void QGCApplication::_checkForNewVersion() { #ifndef __mobile__ if (!_runningUnitTests) { if (_parseVersionText(applicationVersion(), _majorVersion, _minorVersion, _buildVersion)) { QString versionCheckFile = toolbox()->corePlugin()->stableVersionCheckFileUrl(); if (!versionCheckFile.isEmpty()) { QGCFileDownload* download = new QGCFileDownload(this); connect(download, &QGCFileDownload::downloadComplete, this, &QGCApplication::_qgcCurrentStableVersionDownloadComplete); download->download(versionCheckFile); } } } #endif } void QGCApplication::_qgcCurrentStableVersionDownloadComplete(QString /*remoteFile*/, QString localFile, QString errorMsg) { #ifdef __mobile__ Q_UNUSED(localFile) Q_UNUSED(errorMsg) #else if (errorMsg.isEmpty()) { QFile versionFile(localFile); if (versionFile.open(QIODevice::ReadOnly)) { QTextStream textStream(&versionFile); QString version = textStream.readLine(); qDebug() << version; int majorVersion, minorVersion, buildVersion; if (_parseVersionText(version, majorVersion, minorVersion, buildVersion)) { if (_majorVersion < majorVersion || (_majorVersion == majorVersion && _minorVersion < minorVersion) || (_majorVersion == majorVersion && _minorVersion == minorVersion && _buildVersion < buildVersion)) { showAppMessage(tr("There is a newer version of %1 available. You can download it from %2.").arg(applicationName()).arg(toolbox()->corePlugin()->stableDownloadLocation()), tr("New Version Available")); } } } } else { qDebug() << "Download QGC stable version failed" << errorMsg; } sender()->deleteLater(); #endif } bool QGCApplication::_parseVersionText(const QString& versionString, int& majorVersion, int& minorVersion, int& buildVersion) { QRegularExpression regExp("v(\\d+)\\.(\\d+)\\.(\\d+)"); QRegularExpressionMatch match = regExp.match(versionString); if (match.hasMatch() && match.lastCapturedIndex() == 3) { majorVersion = match.captured(1).toInt(); minorVersion = match.captured(2).toInt(); buildVersion = match.captured(3).toInt(); return true; } return false; } void QGCApplication::_onGPSConnect() { _gpsRtkFactGroup->connected()->setRawValue(true); } void QGCApplication::_onGPSDisconnect() { _gpsRtkFactGroup->connected()->setRawValue(false); } void QGCApplication::_gpsSurveyInStatus(float duration, float accuracyMM, double latitude, double longitude, float altitude, bool valid, bool active) { _gpsRtkFactGroup->currentDuration()->setRawValue(duration); _gpsRtkFactGroup->currentAccuracy()->setRawValue(static_cast(accuracyMM) / 1000.0); _gpsRtkFactGroup->currentLatitude()->setRawValue(latitude); _gpsRtkFactGroup->currentLongitude()->setRawValue(longitude); _gpsRtkFactGroup->currentAltitude()->setRawValue(altitude); _gpsRtkFactGroup->valid()->setRawValue(valid); _gpsRtkFactGroup->active()->setRawValue(active); } void QGCApplication::_gpsNumSatellites(int numSatellites) { _gpsRtkFactGroup->numSatellites()->setRawValue(numSatellites); } QString QGCApplication::cachedParameterMetaDataFile(void) { QSettings settings; QDir parameterDir = QFileInfo(settings.fileName()).dir(); return parameterDir.filePath(QStringLiteral("ParameterFactMetaData.xml")); } QString QGCApplication::cachedAirframeMetaDataFile(void) { QSettings settings; QDir airframeDir = QFileInfo(settings.fileName()).dir(); return airframeDir.filePath(QStringLiteral("PX4AirframeFactMetaData.xml")); }