QGCApplication
//QGCApplication.h
#pragma once
#include <QApplication>
#include <QTimer>
#include <QElapsedTimer>
#include <QMap>
#include <QSet>
#include <QMetaMethod>
#include <QMetaObject>
// These private headers are require to implement the signal compress support below
#include <private/qthread_p.h>
#include <private/qobject_p.h>
#include "LinkConfiguration.h"
#include "MAVLinkProtocol.h"
#include "FlightMapSettings.h"
#include "FirmwarePluginManager.h"
#include "MultiVehicleManager.h"
#include "JoystickManager.h"
#include "AudioOutput.h"
#include "UASMessageHandler.h"
#include "FactSystem.h"
#include "GPSRTKFactGroup.h"
#ifdef QGC_RTLAB_ENABLED
#include "OpalLink.h"
#endif
// Work around circular header includes
class QQmlApplicationEngine;
class QGCSingleton;
class QGCToolbox;
/**
* @brief The main application and management class.
*
* This class is started by the main method and provides
* the central management unit of the groundstation application.
*
* Needs QApplication base to support QtCharts module. This way
* we avoid application crashing on 5.12 when using the module.
*
* Note: `lastWindowClosed` will be sent by MessageBox popups and other
* dialogs, that are spawned in QML, when they are closed
**/
class QGCApplication : public QApplication
{
Q_OBJECT
public:
QGCApplication(int &argc, char* argv[], bool unitTesting);
~QGCApplication();
/// @brief Sets the persistent flag to delete all settings the next time QGroundControl is started.
void deleteAllSettingsNextBoot(void);
/// @brief Clears the persistent flag to delete all settings the next time QGroundControl is started.
void clearDeleteAllSettingsNextBoot(void);
/// @brief Returns true if unit tests are being run
bool runningUnitTests(void) const{ return _runningUnitTests; }
/// @brief Returns true if Qt debug output should be logged to a file
bool logOutput(void) const{ return _logOutput; }
/// Used to report a missing Parameter. Warning will be displayed to user. Method may be called
/// multiple times.
void reportMissingParameter(int componentId, const QString& name);
/// @return true: Fake ui into showing mobile interface
bool fakeMobile(void) const { return _fakeMobile; }
// Still working on getting rid of this and using dependency injection instead for everything
QGCToolbox* toolbox(void) { return _toolbox; }
/// Do we have Bluetooth Support?
bool isBluetoothAvailable() const{ return _bluetoothAvailable; }
/// Is Internet available?
bool isInternetAvailable();
FactGroup* gpsRtkFactGroup(void) { return _gpsRtkFactGroup; }
QTranslator& qgcJSONTranslator(void) { return _qgcTranslatorJSON; }
void setLanguage();
QQuickWindow* mainRootWindow();
uint64_t msecsSinceBoot(void) { return _msecsElapsedTime.elapsed(); }
/// Registers the signal such that only the last duplicate signal added is left in the queue.
void addCompressedSignal(const QMetaMethod & method);
void removeCompressedSignal(const QMetaMethod & method);
bool event(QEvent *e) override;
static QString cachedParameterMetaDataFile(void);
static QString cachedAirframeMetaDataFile(void);
public slots:
/// You can connect to this slot to show an information message box from a different thread.
void informationMessageBoxOnMainThread(const QString& title, const QString& msg);
/// You can connect to this slot to show a warning message box from a different thread.
void warningMessageBoxOnMainThread(const QString& title, const QString& msg);
/// You can connect to this slot to show a critical message box from a different thread.
void criticalMessageBoxOnMainThread(const QString& title, const QString& msg);
void showSetupView();
void qmlAttemptWindowClose();
/// Save the specified telemetry Log
void saveTelemetryLogOnMainThread(QString tempLogfile);
/// Check that the telemetry save path is set correctly
void checkTelemetrySavePathOnMainThread();
/// Get current language
const QLocale getCurrentLanguage() { return _locale; }
/// Show non-modal vehicle message to the user
void showCriticalVehicleMessage(const QString& message);
/// Show modal application message to the user
void showAppMessage(const QString& message, const QString& title = QString());
/// Show command application message to the user
void showCommandDialog(const QString& message, int timeout);
/// Show modal application message to the user about the need for a reboot. Multiple messages will be supressed if they occur
/// one after the other.
void showRebootAppMessage(const QString& message, const QString& title = QString());
signals:
/// This is connected to MAVLinkProtocol::checkForLostLogFiles. We signal this to ourselves to call the slot
/// on the MAVLinkProtocol thread;
void checkForLostLogFiles ();
void languageChanged (const QLocale locale);
public:
// Although public, these methods are internal and should only be called by UnitTest code
/// @brief Perform initialize which is common to both normal application running and unit tests.
/// Although public should only be called by main.
void _initCommon();
/// @brief Initialize the application for normal application boot. Or in other words we are not going to run
/// unit tests. Although public should only be called by main.
bool _initForNormalAppBoot();
/// @brief Initialize the application for normal application boot. Or in other words we are not going to run
/// unit tests. Although public should only be called by main.
bool _initForUnitTests();
static QGCApplication* _app; ///< Our own singleton. Should be reference directly by qgcApp
bool isErrorState() const { return _error; }
QQmlApplicationEngine* qmlAppEngine() { return _qmlAppEngine; }
public:
// Although public, these methods are internal and should only be called by UnitTest code
/// Shutdown the application object
void _shutdown();
bool _checkTelemetrySavePath(bool useMessageBox);
private slots:
void _missingParamsDisplay (void);
void _qgcCurrentStableVersionDownloadComplete (QString remoteFile, QString localFile, QString errorMsg);
bool _parseVersionText (const QString& versionString, int& majorVersion, int& minorVersion, int& buildVersion);
void _onGPSConnect (void);
void _onGPSDisconnect (void);
void _gpsSurveyInStatus (float duration, float accuracyMM, double latitude, double longitude, float altitude, bool valid, bool active);
void _gpsNumSatellites (int numSatellites);
void _showDelayedAppMessages (void);
private:
QObject* _rootQmlObject ();
void _checkForNewVersion ();
void _exitWithError (QString errorMessage);
// Overrides from QApplication
bool compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) override;
bool _runningUnitTests; ///< true: running unit tests, false: normal app
static const int _missingParamsDelayedDisplayTimerTimeout = 1000; ///< Timeout to wait for next missing fact to come in before display
QTimer _missingParamsDelayedDisplayTimer; ///< Timer use to delay missing fact display
QList<QPair<int,QString>> _missingParams; ///< List of missing parameter component id:name
QQmlApplicationEngine* _qmlAppEngine = nullptr;
bool _logOutput = false; ///< true: Log Qt debug output to file
bool _fakeMobile = false; ///< true: Fake ui into displaying mobile interface
bool _settingsUpgraded = false; ///< true: Settings format has been upgrade to new version
int _majorVersion = 0;
int _minorVersion = 0;
int _buildVersion = 0;
GPSRTKFactGroup* _gpsRtkFactGroup = nullptr;
QGCToolbox* _toolbox = nullptr;
QQuickWindow* _mainRootWindow = nullptr;
bool _bluetoothAvailable = false;
QTranslator _qgcTranslatorSourceCode; ///< translations for source code C++/Qml
QTranslator _qgcTranslatorJSON; ///< translations for json files
QTranslator _qgcTranslatorQtLibs; ///< tranlsations for Qt libraries
QLocale _locale;
bool _error = false;
QElapsedTimer _msecsElapsedTime;
QList<QPair<QString /* title */, QString /* message */>> _delayedAppMessages;
class CompressedSignalList {
Q_DISABLE_COPY(CompressedSignalList)
public:
CompressedSignalList() {}
void add (const QMetaMethod & method);
void remove (const QMetaMethod & method);
bool contains (const QMetaObject * metaObject, int signalIndex);
private:
static int _signalIndex(const QMetaMethod & method);
QMap<const QMetaObject*, QSet<int> > _signalMap;
};
CompressedSignalList _compressedSignals;
static const char* _settingsVersionKey; ///< Settings key which hold settings version
static const char* _deleteAllSettingsKey; ///< If this settings key is set on boot, all settings will be deleted
/// Unit Test have access to creating and destroying singletons
friend class UnitTest;
};
/// @brief Returns the QGCApplication object singleton.
QGCApplication* qgcApp(void);
//QGCApplication.cc
/**
* @file
* @brief Implementation of class QGCApplication
*
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#include <QFile>
#include <QRegularExpression>
#include <QFontDatabase>
#include <QQuickWindow>
#include <QQuickImageProvider>
#include <QQuickStyle>
#ifdef QGC_ENABLE_BLUETOOTH
#include <QBluetoothLocalDevice>
#endif
#include <QDebug>
#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_DISABLE_MAVLINK_INSPECTOR)
#include "MAVLinkInspectorController.h"
#endif
#include "HorizontalFactValueGrid.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"
#include "QGCMAVLink.h"
#include "VehicleLinkManager.h"
#include "Autotune.h"
#include "CsvDataModel.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 <unistd.h>
#include <sys/types.h>
#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* mavlinkSingletonFactory(QQmlEngine*, QJSEngine*)
{
return new QGCMAVLink();
}
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.<br/><br/>"
"If you are having serial port issues on Ubuntu, execute the following commands to fix most issues:<br/>"
"<pre>sudo usermod -a -G dialout $USER<br/>"
"sudo apt-get remove modemmanager</pre>").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.<br/><br/>"
"If you are using Ubuntu, execute the following commands to fix these issues:<br/>"
"<pre>sudo usermod -a -G dialout $USER<br/>"
"sudo apt-get remove modemmanager</pre>")));
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(APP_VERSION_STR));
// 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");
QLocale::Language possibleLocale = AppSettings::_qLocaleLanguageID();
if (possibleLocale != QLocale::AnyLanguage) {
_locale = QLocale(possibleLocale);
}
//-- We have specific fonts for Korean
if(_locale == QLocale::Korean) {
qCDebug(LocalizationLog) << "Loading Korean fonts" <<