diff --git a/src/Comms/LinkManager.cc b/src/Comms/LinkManager.cc index d3796dbbf800..765132583eff 100644 --- a/src/Comms/LinkManager.cc +++ b/src/Comms/LinkManager.cc @@ -410,80 +410,6 @@ void LinkManager::_addMAVLinkForwardingLink() _createDynamicForwardLink(_mavlinkForwardingLinkName, hostName); } -#ifdef QGC_ZEROCONF_ENABLED -void LinkManager::_addZeroConfAutoConnectLink() -{ - if (!_autoConnectSettings->autoConnectZeroConf()->rawValue().toBool()) { - return; - } - - static QSharedPointer server; - static QSharedPointer browser; - server.reset(new QMdnsEngine::Server()); - browser.reset(new QMdnsEngine::Browser(server.get(), QMdnsEngine::MdnsBrowseType)); - - const auto checkIfConnectionLinkExist = [this](LinkConfiguration::LinkType linkType, const QString &linkName) { - for (const SharedLinkInterfacePtr &link : std::as_const(_rgLinks)) { - const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration(); - if ((linkConfig->type() == linkType) && (linkConfig->name() == linkName)) { - return true; - } - } - - return false; - }; - - (void) connect(browser.get(), &QMdnsEngine::Browser::serviceAdded, this, [checkIfConnectionLinkExist, this](const QMdnsEngine::Service &service) { - qCDebug(LinkManagerLog) << "Found Zero-Conf:" << service.type() << service.name() << service.hostname() << service.port() << service.attributes(); - - if (!service.type().startsWith("_mavlink")) { - qCWarning(LinkManagerLog) << "Invalid ZeroConf SericeType" << service.type(); - return; - } - - // Windows doesnt accept trailling dots in mdns - // http://www.dns-sd.org/trailingdotsindomainnames.html - QString hostname = service.hostname(); - if (hostname.endsWith('.')) { - hostname.chop(1); - } - - if (service.type().startsWith("_mavlink._udp")) { - static const QString udpName = QStringLiteral("ZeroConf UDP"); - if (checkIfConnectionLinkExist(LinkConfiguration::TypeUdp, udpName)) { - qCDebug(LinkManagerLog) << "Connection already exist"; - return; - } - - UDPConfiguration *const link = new UDPConfiguration(udpName); - link->addHost(hostname, service.port()); - link->setAutoConnect(true); - link->setDynamic(true); - SharedLinkConfigurationPtr config = addConfiguration(link); - if (!createConnectedLink(config)) { - qCWarning(LinkManagerLog) << "Failed to create" << udpName; - } - } else if (service.type().startsWith("_mavlink._tcp")) { - static QString tcpName = QStringLiteral("ZeroConf TCP"); - if (checkIfConnectionLinkExist(LinkConfiguration::TypeTcp, tcpName)) { - qCDebug(LinkManagerLog) << "Connection already exist"; - return; - } - - TCPConfiguration *const link = new TCPConfiguration(tcpName); - link->setHost(hostname); - link->setPort(service.port()); - link->setAutoConnect(true); - link->setDynamic(true); - SharedLinkConfigurationPtr config = addConfiguration(link); - if (!createConnectedLink(config)) { - qCWarning(LinkManagerLog) << "Failed to create" << tcpName; - } - } - }); -} -#endif - void LinkManager::_updateAutoConnectLinks() { if (_connectionsSuspended) { @@ -744,6 +670,89 @@ void LinkManager::_createDynamicForwardLink(const char *linkName, const QString qCDebug(LinkManagerLog) << "New dynamic MAVLink forwarding port added:" << linkName << " hostname:" << hostName; } +void LinkManager::resetMavlinkSigning() +{ + for (const SharedLinkInterfacePtr &sharedLink: _rgLinks) { + sharedLink->initMavlinkSigning(); + } +} + +#ifdef QGC_ZEROCONF_ENABLED + +void LinkManager::_addZeroConfAutoConnectLink() +{ + if (!_autoConnectSettings->autoConnectZeroConf()->rawValue().toBool()) { + return; + } + + static QSharedPointer server; + static QSharedPointer browser; + server.reset(new QMdnsEngine::Server()); + browser.reset(new QMdnsEngine::Browser(server.get(), QMdnsEngine::MdnsBrowseType)); + + const auto checkIfConnectionLinkExist = [this](LinkConfiguration::LinkType linkType, const QString &linkName) { + for (const SharedLinkInterfacePtr &link : std::as_const(_rgLinks)) { + const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration(); + if ((linkConfig->type() == linkType) && (linkConfig->name() == linkName)) { + return true; + } + } + + return false; + }; + + (void) connect(browser.get(), &QMdnsEngine::Browser::serviceAdded, this, [checkIfConnectionLinkExist, this](const QMdnsEngine::Service &service) { + qCDebug(LinkManagerLog) << "Found Zero-Conf:" << service.type() << service.name() << service.hostname() << service.port() << service.attributes(); + + if (!service.type().startsWith("_mavlink")) { + qCWarning(LinkManagerLog) << "Invalid ZeroConf SericeType" << service.type(); + return; + } + + // Windows doesnt accept trailling dots in mdns + // http://www.dns-sd.org/trailingdotsindomainnames.html + QString hostname = service.hostname(); + if (hostname.endsWith('.')) { + hostname.chop(1); + } + + if (service.type().startsWith("_mavlink._udp")) { + static const QString udpName = QStringLiteral("ZeroConf UDP"); + if (checkIfConnectionLinkExist(LinkConfiguration::TypeUdp, udpName)) { + qCDebug(LinkManagerLog) << "Connection already exist"; + return; + } + + UDPConfiguration *const link = new UDPConfiguration(udpName); + link->addHost(hostname, service.port()); + link->setAutoConnect(true); + link->setDynamic(true); + SharedLinkConfigurationPtr config = addConfiguration(link); + if (!createConnectedLink(config)) { + qCWarning(LinkManagerLog) << "Failed to create" << udpName; + } + } else if (service.type().startsWith("_mavlink._tcp")) { + static QString tcpName = QStringLiteral("ZeroConf TCP"); + if (checkIfConnectionLinkExist(LinkConfiguration::TypeTcp, tcpName)) { + qCDebug(LinkManagerLog) << "Connection already exist"; + return; + } + + TCPConfiguration *const link = new TCPConfiguration(tcpName); + link->setHost(hostname); + link->setPort(service.port()); + link->setAutoConnect(true); + link->setDynamic(true); + SharedLinkConfigurationPtr config = addConfiguration(link); + if (!createConnectedLink(config)) { + qCWarning(LinkManagerLog) << "Failed to create" << tcpName; + } + } + }); +} + +#endif // QGC_ZEROCONF_ENABLED + bool LinkManager::isLinkUSBDirect(const LinkInterface *link) { #ifndef QGC_NO_SERIAL_LINK @@ -766,14 +775,18 @@ bool LinkManager::isLinkUSBDirect(const LinkInterface *link) return false; } -void LinkManager::resetMavlinkSigning() +#ifndef QGC_NO_SERIAL_LINK // Serial Only Functions + +bool LinkManager::_isSerialPortConnected() const { - for (const SharedLinkInterfacePtr &sharedLink: _rgLinks) { - sharedLink->initMavlinkSigning(); + for (const SharedLinkInterfacePtr &link: _rgLinks) { + if (qobject_cast(link.get())) { + return true; + } } -} -#ifndef QGC_NO_SERIAL_LINK // Serial Only Functions + return false; +} void LinkManager::_filterCompositePorts(QList &portList) { @@ -997,15 +1010,4 @@ QStringList LinkManager::serialBaudRates() return SerialConfiguration::supportedBaudRates(); } -bool LinkManager::_isSerialPortConnected() const -{ - for (const SharedLinkInterfacePtr &link: _rgLinks) { - if (qobject_cast(link.get())) { - return true; - } - } - - return false; -} - #endif // QGC_NO_SERIAL_LINK diff --git a/src/Comms/LinkManager.h b/src/Comms/LinkManager.h index 30ee45584c84..869a9c4773fb 100644 --- a/src/Comms/LinkManager.h +++ b/src/Comms/LinkManager.h @@ -34,10 +34,10 @@ class SerialLink; class UDPConfiguration; class UdpIODevice; -/// @brief Manage communication links -/// The Link Manager organizes the physical Links. It can manage arbitrary -/// links and takes care of connecting them as well assigning the correct -/// protocol instance to transport the link data into the application. +/// Manage communication links +/// The Link Manager organizes the physical Links. It can manage arbitrary +/// links and takes care of connecting them as well assigning the correct +/// protocol instance to transport the link data into the application. class LinkManager : public QObject { Q_OBJECT @@ -175,9 +175,9 @@ private slots: Q_PROPERTY(QStringList serialPorts READ serialPorts NOTIFY commPortsChanged) public: - static QStringList serialBaudRates(); QStringList serialPortStrings(); QStringList serialPorts(); + static QStringList serialBaudRates(); signals: void commPortStringsChanged(); diff --git a/src/Comms/TCPLink.cc b/src/Comms/TCPLink.cc index 668be2111bb0..91e5ccfb4ad7 100644 --- a/src/Comms/TCPLink.cc +++ b/src/Comms/TCPLink.cc @@ -16,7 +16,7 @@ #include #include -QGC_LOGGING_CATEGORY(TCPLinkLog, "test.comms.tcplink") +QGC_LOGGING_CATEGORY(TCPLinkLog, "qgc.comms.tcplink") namespace { constexpr int CONNECT_TIMEOUT_MS = 5000; diff --git a/src/FactSystem/ParameterManager.cc b/src/FactSystem/ParameterManager.cc index 9f44e8bf0666..12268e5b6e51 100644 --- a/src/FactSystem/ParameterManager.cc +++ b/src/FactSystem/ParameterManager.cc @@ -812,7 +812,8 @@ void ParameterManager::_writeLocalParamCache(int vehicleId, int componentId) QDir ParameterManager::parameterCacheDir() { const QString spath(QFileInfo(QSettings().fileName()).dir().absolutePath()); - return (spath + QDir::separator() + QStringLiteral("ParamCache")); + const QDir cacheDir(spath + QDir::separator() + QStringLiteral("ParamCache")); + return cacheDir; } QString ParameterManager::parameterCacheFile(int vehicleId, int componentId) diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc index d12c67bb56e4..f6799998b048 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -9,13 +9,16 @@ #include "QGCApplication.h" +#include #include #include #include #include #include +#include #include #include +#include #include #include #include @@ -23,17 +26,17 @@ #include #include -#include - -#include "QGCLogging.h" +#include "AppSettings.h" #include "AudioOutput.h" #include "FollowMe.h" #include "JoystickManager.h" #include "JsonHelper.h" #include "LinkManager.h" #include "MAVLinkProtocol.h" +#include "MavlinkSettings.h" #include "MultiVehicleManager.h" #include "ParameterManager.h" +#include "Platform.h" #include "PositionManager.h" #include "QGCCommandLineParser.h" #include "QGCCorePlugin.h" @@ -41,19 +44,13 @@ #include "QGCImageProvider.h" #include "QGCLoggingCategory.h" #include "SettingsManager.h" -#include "MavlinkSettings.h" -#include "AppSettings.h" -#include "UDPLink.h" #include "Vehicle.h" -#include "VehicleComponent.h" #include "VideoManager.h" -#ifndef QGC_NO_SERIAL_LINK -#include "SerialLink.h" -#endif - QGC_LOGGING_CATEGORY(QGCApplicationLog, "qgc.qgcapplication") +using namespace Qt::StringLiterals; + QGCApplication::QGCApplication(int &argc, char *argv[], const QGCCommandLineParser::CommandLineParseResult &cli) : QApplication(argc, argv) , _runningUnitTests(cli.runningUnitTests) @@ -61,40 +58,59 @@ QGCApplication::QGCApplication(int &argc, char *argv[], const QGCCommandLinePars , _fakeMobile(cli.fakeMobile) , _logOutput(cli.logOutput) , _systemId(cli.systemId.value_or(0)) + , _qgcTranslatorSourceCode(std::make_unique()) + , _qgcTranslatorQtLibs(std::make_unique()) + , _currentLocale(QLocale::system()) { + qCDebug(QGCApplicationLog) << this; + + // Start elapsed timer immediately _msecsElapsedTime.start(); + // Install event filter for signal compression (replaces deprecated compressEvent) + installEventFilter(this); + // Setup for network proxy support QNetworkProxyFactory::setUseSystemConfiguration(true); - bool fClearSettingsOptions = cli.clearSettingsOptions; // Clear stored settings - const bool fClearCache = cli.clearCache; // Clear parameter/airframe caches - const QString loggingOptions = cli.loggingOptions.value_or(QString("")); + const bool fClearSettingsOptions = cli.clearSettingsOptions; + const bool fClearCache = cli.clearCache; + const QString loggingOptions = cli.loggingOptions.value_or(QString()); - // Set up timer for delayed missing fact display + // Set up timer for delayed missing fact display with proper parent _missingParamsDelayedDisplayTimer.setSingleShot(true); _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout); - (void) connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, &QGCApplication::_missingParamsDisplay); + (void) connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, &QGCApplication::_missingParamsDisplay, Qt::QueuedConnection); // Set application information QString applicationName; if (_runningUnitTests || _simpleBootTest) { // 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_APP_NAME); + applicationName = QStringLiteral("%1_unittest").arg(QStringLiteral(QGC_APP_NAME)); } else { #ifdef QGC_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_APP_NAME); + applicationName = QStringLiteral("%1 Daily").arg(QStringLiteral(QGC_APP_NAME)); #else - applicationName = QGC_APP_NAME; + applicationName = QStringLiteral(QGC_APP_NAME); #endif } + setApplicationName(applicationName); - setOrganizationName(QGC_ORG_NAME); - setOrganizationDomain(QGC_ORG_DOMAIN); - setApplicationVersion(QString(QGC_APP_VERSION_STR)); + setOrganizationName(QStringLiteral(QGC_ORG_NAME)); + setOrganizationDomain(QStringLiteral(QGC_ORG_DOMAIN)); + + const QString versionStr = QStringLiteral(QGC_APP_VERSION_STR); + setApplicationVersion(versionStr); + _appVersion = _parseVersionText(versionStr); + + if (_appVersion.isNull()) { + qCWarning(QGCApplicationLog) << "Failed to parse application version:" << versionStr; + // Set a default version to prevent issues + _appVersion = QVersionNumber(1, 0, 0); + } // Set settings format QSettings::setDefaultFormat(QSettings::IniFormat); @@ -102,50 +118,65 @@ QGCApplication::QGCApplication(int &argc, char *argv[], const QGCCommandLinePars qCDebug(QGCApplicationLog) << "Settings location" << settings.fileName() << "Is writable?:" << settings.isWritable(); if (!settings.isWritable()) { - qCWarning(QGCApplicationLog) << "Setings location is not writable"; + qCWarning(QGCApplicationLog) << "Settings location is not writable"; } // The setting will delete all settings on this boot - fClearSettingsOptions |= settings.contains(_deleteAllSettingsKey); + bool fClearSettings = fClearSettingsOptions || settings.contains(_deleteAllSettingsKey); if (_runningUnitTests || _simpleBootTest) { // Unit tests run with clean settings - fClearSettingsOptions = true; + fClearSettings = true; } - if (fClearSettingsOptions) { + if (fClearSettings) { // User requested settings to be cleared on command line settings.clear(); + settings.remove(_deleteAllSettingsKey); // Clear parameter cache - QDir paramDir(ParameterManager::parameterCacheDir()); - paramDir.removeRecursively(); - paramDir.mkpath(paramDir.absolutePath()); + QDir paramDir = ParameterManager::parameterCacheDir(); + if (paramDir.exists()) { + (void) paramDir.removeRecursively(); + (void) 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. + // Determine if upgrade message for settings version bump is required if (settings.contains(_settingsVersionKey)) { - if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) { + bool ok = false; + const int version = settings.value(_settingsVersionKey).toInt(&ok); + if (ok && (version != QGC_SETTINGS_VERSION)) { settings.clear(); _settingsUpgraded = true; + emit settingsUpgraded(); } } } settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION); if (fClearCache) { - QDir dir(ParameterManager::parameterCacheDir()); - dir.removeRecursively(); + const QDir paramCacheDir = ParameterManager::parameterCacheDir(); + if (paramCacheDir.exists()) { + QDir dir(paramCacheDir); + (void) dir.removeRecursively(); + } + QFile airframe(cachedAirframeMetaDataFile()); - airframe.remove(); + if (airframe.exists()) { + (void) airframe.remove(); + } + QFile parameter(cachedParameterMetaDataFile()); - parameter.remove(); + if (parameter.exists()) { + (void) parameter.remove(); + } } // Set up our logging filters QGCLoggingCategoryRegister::instance()->setFilterRulesFromSettings(loggingOptions); - // We need to set language as early as possible prior to loading on JSON files. + // We need to set language as early as possible prior to loading JSON files. + // This is safe here because no threads have been created yet setLanguage(); #ifndef QGC_DAILY_BUILD @@ -153,89 +184,57 @@ QGCApplication::QGCApplication(int &argc, char *argv[], const QGCCommandLinePars #endif } -void QGCApplication::setLanguage() -{ - _locale = QLocale::system(); - qCDebug(QGCApplicationLog) << "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::_qLocaleLanguageEarlyAccess(); - if (possibleLocale != QLocale::AnyLanguage) { - _locale = QLocale(possibleLocale); - } - //-- We have specific fonts for Korean - if (_locale == QLocale::Korean) { - qCDebug(QGCApplicationLog) << "Loading Korean fonts" << _locale.name(); - if(QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Regular") < 0) { - qCWarning(QGCApplicationLog) << "Could not load /fonts/NanumGothic-Regular font"; - } - if(QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Bold") < 0) { - qCWarning(QGCApplicationLog) << "Could not load /fonts/NanumGothic-Bold font"; - } - } - qCDebug(QGCApplicationLog) << "Loading localizations for" << _locale.name(); - removeTranslator(JsonHelper::translator()); - removeTranslator(&_qgcTranslatorSourceCode); - removeTranslator(&_qgcTranslatorQtLibs); - if (_locale.name() != "en_US") { - QLocale::setDefault(_locale); - if (_qgcTranslatorQtLibs.load("qt_" + _locale.name(), QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { - installTranslator(&_qgcTranslatorQtLibs); - } else { - qCWarning(QGCApplicationLog) << "Qt lib localization for" << _locale.name() << "is not present"; - } - if (_qgcTranslatorSourceCode.load(_locale, QLatin1String("qgc_source_"), "", ":/i18n")) { - installTranslator(&_qgcTranslatorSourceCode); - } else { - qCWarning(QGCApplicationLog) << "Error loading source localization for" << _locale.name(); - } - if (JsonHelper::translator()->load(_locale, QLatin1String("qgc_json_"), "", ":/i18n")) { - installTranslator(JsonHelper::translator()); - } else { - qCWarning(QGCApplicationLog) << "Error loading json localization for" << _locale.name(); - } - } - - if (_qmlAppEngine) { - _qmlAppEngine->retranslate(); - } - - emit languageChanged(_locale); -} - QGCApplication::~QGCApplication() { - + qCDebug(QGCApplicationLog) << this; } void QGCApplication::init() { SettingsManager::instance()->init(); + if (_systemId > 0) { qCDebug(QGCApplicationLog) << "Setting MAVLink System ID to:" << _systemId; SettingsManager::instance()->mavlinkSettings()->gcsMavlinkSystemID()->setRawValue(_systemId); } - // 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) { + // Load fonts - although this should really be in _initForNormalAppBoot, + // putting it here allows us to create unit tests which pop up more easily + if (QFontDatabase::addApplicationFont(QStringLiteral(":/fonts/opensans")) < 0) { qCWarning(QGCApplicationLog) << "Could not load /fonts/opensans font"; } - if (QFontDatabase::addApplicationFont(":/fonts/opensans-demibold") < 0) { + if (QFontDatabase::addApplicationFont(QStringLiteral(":/fonts/opensans-demibold")) < 0) { qCWarning(QGCApplicationLog) << "Could not load /fonts/opensans-demibold font"; } if (_simpleBootTest) { // Since GStream builds are so problematic we initialize video during the simple boot test - // to make sure it works and verfies plugin availability. + // to make sure it works and verifies plugin availability. _initVideo(); } else if (!_runningUnitTests) { _initForNormalAppBoot(); } + + // Mark initialization complete - after this point, changing QLocale::setDefault() is unsafe + _appInitialized = true; } void QGCApplication::_initVideo() { #ifdef QGC_GST_STREAMING + QOpenGLContext testContext; + if (!testContext.create()) { + qCWarning(QGCApplicationLog) << "OpenGL context creation failed - video may not work"; + } else { + const QSurfaceFormat format = testContext.format(); + if (format.majorVersion() < 2) { + qCWarning(QGCApplicationLog) << "OpenGL version too old:" + << format.majorVersion() << "." << format.minorVersion() + << "- video may not work properly"; + } + } + // Gstreamer video playback requires OpenGL QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); #endif @@ -249,60 +248,60 @@ void QGCApplication::_initForNormalAppBoot() { _initVideo(); // GStreamer must be initialized before QmlEngine - QQuickStyle::setStyle("Basic"); + QQuickStyle::setStyle(QStringLiteral("Basic")); + QGCCorePlugin::instance()->init(); MAVLinkProtocol::instance()->init(); MultiVehicleManager::instance()->init(); + _qmlAppEngine = QGCCorePlugin::instance()->createQmlApplicationEngine(this); - QObject::connect(_qmlAppEngine, &QQmlApplicationEngine::objectCreationFailed, this, QCoreApplication::quit, Qt::QueuedConnection); + if (!_qmlAppEngine) { + qCCritical(QGCApplicationLog) << "Failed to create QML application engine"; + return; + } + + (void) connect(_qmlAppEngine, &QQmlApplicationEngine::objectCreationFailed, this, &QCoreApplication::quit, Qt::QueuedConnection); + QGCCorePlugin::instance()->createRootWindow(_qmlAppEngine); AudioOutput::instance()->init(SettingsManager::instance()->appSettings()->audioMuted()); FollowMe::instance()->init(); QGCPositionManager::instance()->init(); LinkManager::instance()->init(); - VideoManager::instance()->init(mainRootWindow()); + + QQuickWindow *rootWindow = mainRootWindow(); + if (rootWindow) { + VideoManager::instance()->init(rootWindow); + } else { + qCWarning(QGCApplicationLog) << "No root window available for VideoManager"; + } // Image provider for Optical Flow _qmlAppEngine->addImageProvider(_qgcImageProviderId, new QGCImageProvider()); // Set the window icon now that custom plugin has a chance to override it #ifdef Q_OS_LINUX - QUrl windowIcon = QUrl("qrc:/res/qgroundcontrol.ico"); + QUrl windowIcon = QUrl(QStringLiteral("qrc:/res/qgroundcontrol.ico")); windowIcon = _qmlAppEngine->interceptUrl(windowIcon, QQmlAbstractUrlInterceptor::UrlString); // The interceptor needs "qrc:/path" but QIcon expects ":/path" - setWindowIcon(QIcon(":" + windowIcon.path())); + const QString iconPath = windowIcon.path(); + if (!iconPath.isEmpty()) { + setWindowIcon(QIcon(":" + iconPath)); + } #endif // Safe to show popup error messages now that main window is created _showErrorsInToolbar = true; - #ifdef Q_OS_LINUX - #ifndef Q_OS_ANDROID - #ifndef QGC_NO_SERIAL_LINK - if (!_runningUnitTests) { - // Determine if we have the correct permissions to access USB serial devices - QFile permFile("/etc/group"); - if(permFile.open(QIODevice::ReadOnly)) { - while(!permFile.atEnd()) { - const QString line = permFile.readLine(); - if (line.contains("dialout") && !line.contains(getenv("USER"))) { - permFile.close(); - showAppMessage(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
")); - break; - } - } - permFile.close(); - } +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) && !defined(QGC_NO_SERIAL_LINK) + if (!_runningUnitTests) { + QString warningMessage; + Platform::checkSerialPortPermissions(warningMessage); + if (!warningMessage.isEmpty()) { + showAppMessage(warningMessage, tr("Serial Port Permission Warning")); } - #endif - #endif - #endif + } +#endif // Now that main window is up check for lost log files MAVLinkProtocol::instance()->checkForLostLogFiles(); @@ -314,8 +313,10 @@ void QGCApplication::_initForNormalAppBoot() JoystickManager::instance()->init(); if (_settingsUpgraded) { - showAppMessage(tr("The format for %1 saved settings has been modified. " - "Your saved settings have been reset to defaults.").arg(applicationName())); + showAppMessage(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 @@ -336,39 +337,51 @@ void QGCApplication::clearDeleteAllSettingsNextBoot() void QGCApplication::reportMissingParameter(int componentId, const QString &name) { - const QPair missingParam(componentId, name); + if (name.isEmpty()) { + qCWarning(QGCApplicationLog) << "Empty parameter name reported for component" << componentId; + return; + } - if (!_missingParams.contains(missingParam)) { - _missingParams.append(missingParam); + const QPair missingParam(componentId, name); + { + const QMutexLocker locker(&_missingParamsMutex); + if (!_missingParams.contains(missingParam)) { + _missingParams.append(missingParam); + } } _missingParamsDelayedDisplayTimer.start(); } void QGCApplication::_missingParamsDisplay() { - if (_missingParams.isEmpty()) { - return; + QList> paramsCopy; + { + const QMutexLocker locker(&_missingParamsMutex); + if (_missingParams.isEmpty()) { + return; + } + paramsCopy = std::move(_missingParams); + _missingParams.clear(); } - QString params; - for (QPair& missingParam: _missingParams) { - const QString param = QStringLiteral("%1:%2").arg(missingParam.first).arg(missingParam.second); - if (params.isEmpty()) { - params += param; - } else { - params += QStringLiteral(", %1").arg(param); - } + QStringList paramList; + paramList.reserve(paramsCopy.size()); + for (const QPair &missingParam: std::as_const(paramsCopy)) { + paramList.append(QStringLiteral("%1:%2").arg(missingParam.first).arg(missingParam.second)); } - _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)); + 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(paramList.join(QStringLiteral(", ")))); } QObject *QGCApplication::_rootQmlObject() { - if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) { - return _qmlAppEngine->rootObjects()[0]; + if (_qmlAppEngine && !_qmlAppEngine->rootObjects().isEmpty()) { + QObject *rootObject = _qmlAppEngine->rootObjects().first(); + return rootObject; } return nullptr; @@ -376,66 +389,101 @@ QObject *QGCApplication::_rootQmlObject() void QGCApplication::showCriticalVehicleMessage(const QString &message) { + if (message.isEmpty()) { + return; + } + // PreArm messages are handled by Vehicle and shown in Map - if (message.startsWith(QStringLiteral("PreArm")) || message.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive)) { + if (message.startsWith(QStringLiteral("PreArm")) || + message.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive)) { return; } - QObject *const rootQmlObject = _rootQmlObject(); + QObject * const rootQmlObject = _rootQmlObject(); if (rootQmlObject && _showErrorsInToolbar) { QVariant varReturn; - QVariant varMessage = QVariant::fromValue(message); - QMetaObject::invokeMethod(rootQmlObject, "showCriticalVehicleMessage", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, varMessage)); + const QVariant varMessage = QVariant::fromValue(message); + const bool success = QMetaObject::invokeMethod(rootQmlObject, "showCriticalVehicleMessage", + Q_RETURN_ARG(QVariant, varReturn), + Q_ARG(QVariant, varMessage)); + if (!success) { + qCWarning(QGCApplicationLog) << "Failed to invoke"; + } } else if (runningUnitTests() || !_showErrorsInToolbar) { // Unit tests can run without UI - qCDebug(QGCApplicationLog) << "QGCApplication::showCriticalVehicleMessage unittest" << message; + qCDebug(QGCApplicationLog) << "unittest" << message; } else { - qCWarning(QGCApplicationLog) << "Internal error"; + qCWarning(QGCApplicationLog) << "Internal error: no root object available"; } } void QGCApplication::showAppMessage(const QString &message, const QString &title) { + if (message.isEmpty()) { + return; + } + const QString dialogTitle = title.isEmpty() ? applicationName() : title; - QObject *const rootQmlObject = _rootQmlObject(); + 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)); + const QVariant varMessage = QVariant::fromValue(message); + const QVariant varTitle = QVariant::fromValue(dialogTitle); + const bool success = QMetaObject::invokeMethod(rootQmlObject, "_showMessageDialog", + Q_RETURN_ARG(QVariant, varReturn), + Q_ARG(QVariant, varTitle), + Q_ARG(QVariant, varMessage)); + if (!success) { + qCWarning(QGCApplicationLog) << "Failed to invoke _showMessageDialog"; + } } else if (runningUnitTests()) { // Unit tests can run without UI - qCDebug(QGCApplicationLog) << "QGCApplication::showAppMessage unittest title:message" << dialogTitle << message; + qCDebug(QGCApplicationLog) << "unittest title:message" << dialogTitle << message; } else { - // UI isn't ready yet - _delayedAppMessages.append(QPair(dialogTitle, message)); + // UI isn't ready yet - queue for later + { + const QMutexLocker locker(&_delayedMessagesMutex); + _delayedAppMessages.append(QPair(dialogTitle, message)); + } QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages); } } void QGCApplication::showRebootAppMessage(const QString &message, const QString &title) { + static QMutex rebootMutex; static QTime lastRebootMessage; + QMutexLocker locker(&rebootMutex); + const QTime currentTime = QTime::currentTime(); - const QTime previousTime = lastRebootMessage; - lastRebootMessage = currentTime; - if (previousTime.isValid() && (previousTime.msecsTo(currentTime) < (60 * 1000 * 2))) { - // Debounce reboot messages + if (lastRebootMessage.isValid() && + (lastRebootMessage.msecsTo(currentTime) < (60 * 1000 * 2))) { + // Debounce reboot messages - ignore if within 2 minutes return; } + lastRebootMessage = currentTime; + locker.unlock(); + showAppMessage(message, title); } void QGCApplication::_showDelayedAppMessages() { if (_rootQmlObject()) { - for (const QPair& appMsg: _delayedAppMessages) { + QList> messagesCopy; + { + const QMutexLocker locker(&_delayedMessagesMutex); + messagesCopy = std::move(_delayedAppMessages); + _delayedAppMessages.clear(); + } + + for (const QPair& appMsg: std::as_const(messagesCopy)) { showAppMessage(appMsg.second, appMsg.first); } - _delayedAppMessages.clear(); } else { QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages); } @@ -446,21 +494,28 @@ QQuickWindow *QGCApplication::mainRootWindow() if (!_mainRootWindow) { _mainRootWindow = qobject_cast(_rootQmlObject()); } - - return _mainRootWindow; + return _mainRootWindow.data(); } void QGCApplication::showVehicleConfig() { - if (_rootQmlObject()) { - QMetaObject::invokeMethod(_rootQmlObject(), "showVehicleConfig"); + QObject *rootObject = _rootQmlObject(); + if (rootObject) { + const bool success = QMetaObject::invokeMethod(rootObject, "showVehicleConfig"); + if (!success) { + qCWarning(QGCApplicationLog) << "Failed to invoke showVehicleConfig"; + } } } void QGCApplication::qmlAttemptWindowClose() { - if (_rootQmlObject()) { - QMetaObject::invokeMethod(_rootQmlObject(), "attemptWindowClose"); + QObject *rootObject = _rootQmlObject(); + if (rootObject) { + const bool success = QMetaObject::invokeMethod(rootObject, "attemptWindowClose"); + if (!success) { + qCWarning(QGCApplicationLog) << "Failed to invoke attemptWindowClose"; + } } } @@ -470,58 +525,76 @@ void QGCApplication::_checkForNewVersion() return; } - if (!_parseVersionText(applicationVersion(), _majorVersion, _minorVersion, _buildVersion)) { + if (_appVersion.isNull() || (_appVersion.majorVersion() <= 0)) { return; } const QString versionCheckFile = QGCCorePlugin::instance()->stableVersionCheckFileUrl(); - if (!versionCheckFile.isEmpty()) { - QGCFileDownload *const download = new QGCFileDownload(this); - (void) connect(download, &QGCFileDownload::downloadComplete, this, &QGCApplication::_qgcCurrentStableVersionDownloadComplete); - download->download(versionCheckFile); + if (versionCheckFile.isEmpty()) { + return; + } + + // Parent object ensures proper cleanup + QGCFileDownload *download = new QGCFileDownload(this); + (void) connect(download, &QGCFileDownload::downloadComplete, this, &QGCApplication::_qgcCurrentStableVersionDownloadComplete); + if (!download->download(versionCheckFile)) { + download->deleteLater(); + qCWarning(QGCApplicationLog) << "Download QGC stable version failed"; } } void QGCApplication::_qgcCurrentStableVersionDownloadComplete(const QString &remoteFile, const QString &localFile, const QString &errorMsg) { - Q_UNUSED(remoteFile); + Q_UNUSED(remoteFile) if (errorMsg.isEmpty()) { QFile versionFile(localFile); if (versionFile.open(QIODevice::ReadOnly)) { QTextStream textStream(&versionFile); - const QString version = textStream.readLine(); + const QString versionText = textStream.readLine(); + versionFile.close(); - qCDebug(QGCApplicationLog) << version; + qCDebug(QGCApplicationLog) << "Remote version:" << versionText; - 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(QGCCorePlugin::instance()->stableDownloadLocation()), tr("New Version Available")); - } + const QVersionNumber remoteVersion = _parseVersionText(versionText); + if (!remoteVersion.isNull() && _appVersion < remoteVersion) { + const QString message = tr("There is a newer version of %1 available. You can download it from %2.") + .arg(applicationName(), QGCCorePlugin::instance()->stableDownloadLocation()); + showAppMessage(message, tr("New Version Available")); } } } else { - qCDebug(QGCApplicationLog) << "Download QGC stable version failed" << errorMsg; + qCDebug(QGCApplicationLog) << "Download QGC stable version failed:" << errorMsg; } sender()->deleteLater(); } -bool QGCApplication::_parseVersionText(const QString &versionString, int &majorVersion, int &minorVersion, int &buildVersion) +QVersionNumber QGCApplication::_parseVersionText(const QString &versionString) { - static const QRegularExpression regExp("v(\\d+)\\.(\\d+)\\.(\\d+)"); + if (versionString.isEmpty()) { + return QVersionNumber(); + } + + static const QRegularExpression regExp(uR"(v?(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?)"_s); + const 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; + if (match.hasMatch()) { + QList segments; + for (int i = 1; i <= match.lastCapturedIndex(); ++i) { + bool ok = false; + const int value = match.captured(i).toInt(&ok); + if (ok) { + segments.append(value); + } + } + + if (!segments.isEmpty()) { + return QVersionNumber(segments); + } } - return false; + return QVersionNumber(); } QString QGCApplication::cachedParameterMetaDataFile() @@ -538,51 +611,157 @@ QString QGCApplication::cachedAirframeMetaDataFile() return airframeDir.filePath(QStringLiteral("PX4AirframeFactMetaData.xml")); } -int QGCApplication::CompressedSignalList::_signalIndex(const QMetaMethod &method) +QGCImageProvider *QGCApplication::qgcImageProvider() { - if (method.methodType() != QMetaMethod::Signal) { - qCWarning(QGCApplicationLog) << "Internal error:" << Q_FUNC_INFO << "not a signal" << method.methodType(); - return -1; + if (!_qmlAppEngine) { + return nullptr; } - int index = -1; - const QMetaObject *metaObject = method.enclosingMetaObject(); - for (int i=0; i<=method.methodIndex(); i++) { - if (metaObject->method(i).methodType() != QMetaMethod::Signal) { - continue; - } - index++; + return dynamic_cast(_qmlAppEngine->imageProvider(_qgcImageProviderId)); +} + +void QGCApplication::shutdown() +{ + qCDebug(QGCApplicationLog) << "Shutdown started"; + + // Stop any timers + _missingParamsDelayedDisplayTimer.stop(); + + // Clean up video manager first if initialized + if (_videoManagerInitialized) { + VideoManager::instance()->cleanup(); + _videoManagerInitialized = false; } - return index; + // Clean up core plugin + QGCCorePlugin::instance()->cleanup(); + + // Clear window reference + _mainRootWindow = nullptr; + + delete _qmlAppEngine; + + qCDebug(QGCApplicationLog) << "Shutdown complete"; } -void QGCApplication::CompressedSignalList::add(const QMetaMethod &method) +QString QGCApplication::numberToString(quint64 number) const { - const QMetaObject *metaObject = method.enclosingMetaObject(); - const int signalIndex = _signalIndex(method); + return _currentLocale.toString(number); +} - if (signalIndex != -1 && !contains(metaObject, signalIndex)) { - _signalMap[method.enclosingMetaObject()].insert(signalIndex); +QString QGCApplication::bigSizeToString(quint64 size) const +{ + QString result; + const QLocale &locale = _currentLocale; + + if (size < 1024ULL) { + result = locale.toString(size) + "B"; + } else if (size < (1024ULL * 1024)) { + result = locale.toString(static_cast(size) / 1024.0, 'f', 1) + "KB"; + } else if (size < (1024ULL * 1024 * 1024)) { + result = locale.toString(static_cast(size) / (1024.0 * 1024), 'f', 1) + "MB"; + } else if (size < (1024ULL * 1024 * 1024 * 1024)) { + result = locale.toString(static_cast(size) / (1024.0 * 1024 * 1024), 'f', 1) + "GB"; + } else { + result = locale.toString(static_cast(size) / (1024.0 * 1024 * 1024 * 1024), 'f', 1) + "TB"; } + + return result; } -void QGCApplication::CompressedSignalList::remove(const QMetaMethod &method) +QString QGCApplication::bigSizeMBToString(quint64 size_MB) const { - const int signalIndex = _signalIndex(method); - const QMetaObject *const metaObject = method.enclosingMetaObject(); + QString result; + const QLocale &locale = _currentLocale; - if (signalIndex != -1 && _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex)) { - _signalMap[metaObject].remove(signalIndex); - if (_signalMap[metaObject].count() == 0) { - _signalMap.remove(metaObject); - } + if (size_MB < 1024) { + result = locale.toString(static_cast(size_MB), 'f', 0) + " MB"; + } else if (size_MB < (1024ULL * 1024)) { + result = locale.toString(static_cast(size_MB) / 1024.0, 'f', 1) + " GB"; + } else { + result = locale.toString(static_cast(size_MB) / (1024.0 * 1024), 'f', 2) + " TB"; } + + return result; } -bool QGCApplication::CompressedSignalList::contains(const QMetaObject *metaObject, int signalIndex) +void QGCApplication::setLanguage() { - return _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex); + // WARNING: This method is called both at startup (safe) and potentially later (unsafe) + // QLocale::setDefault() should only be called before threads are created + + QLocale locale = QLocale::system(); + qCDebug(QGCApplicationLog) << "System reported locale:" << locale + << "; Name:" << locale.name() + << "; Preferred (used in maps):" + << (!locale.uiLanguages().isEmpty() ? locale.uiLanguages().constFirst() : "None"); + + const QLocale::Language possibleLocale = AppSettings::_qLocaleLanguageEarlyAccess(); + if (possibleLocale != QLocale::AnyLanguage) { + locale = QLocale(possibleLocale); + } + + // Store the selected locale for later use + _currentLocale = locale; + + // Load Korean fonts if needed + if (locale == QLocale::Korean) { + qCDebug(QGCApplicationLog) << "Loading Korean fonts for locale:" << locale.name(); + if (QFontDatabase::addApplicationFont(QStringLiteral(":/fonts/NanumGothic-Regular")) < 0) { + qCWarning(QGCApplicationLog) << "Could not load /fonts/NanumGothic-Regular font"; + } + if (QFontDatabase::addApplicationFont(QStringLiteral(":/fonts/NanumGothic-Bold")) < 0) { + qCWarning(QGCApplicationLog) << "Could not load /fonts/NanumGothic-Bold font"; + } + } + + qCDebug(QGCApplicationLog) << "Loading localizations for:" << locale.name(); + + // Remove existing translators + (void) removeTranslator(JsonHelper::translator()); + (void) removeTranslator(_qgcTranslatorSourceCode.get()); + (void) removeTranslator(_qgcTranslatorQtLibs.get()); + + if ((locale.language() != QLocale::English) || (locale.territory() != QLocale::UnitedStates)) { + // Only set default locale during initial startup (constructor) + // After threads are created, changing the default locale is unsafe + if (!_appInitialized) { + QLocale::setDefault(locale); + } else { + qCWarning(QGCApplicationLog) << "Cannot change default locale after initialization due to thread safety." + << "Translations will be updated but default locale remains unchanged."; + } + + // Qt6 API for translations path + const QString qtTranslationsPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath); + + if (_qgcTranslatorQtLibs->load("qt_" + locale.name(), qtTranslationsPath)) { + (void) installTranslator(_qgcTranslatorQtLibs.get()); + } else { + qCWarning(QGCApplicationLog) << "Qt lib localization for" << locale.name() << "is not present"; + } + + if (_qgcTranslatorSourceCode->load(locale, QStringLiteral("qgc_source_"), QString(), QStringLiteral(":/i18n"))) { + (void) installTranslator(_qgcTranslatorSourceCode.get()); + } else { + qCWarning(QGCApplicationLog) << "Error loading source localization for" << locale.name(); + } + + if (JsonHelper::translator()->load(locale, QStringLiteral("qgc_json_"), QString(), QStringLiteral(":/i18n"))) { + (void) installTranslator(JsonHelper::translator()); + } else { + qCWarning(QGCApplicationLog) << "Error loading json localization for" << locale.name(); + } + } else if (!_appInitialized) { + // Even for en_US, set it as default during startup + QLocale::setDefault(locale); + } + + if (_qmlAppEngine) { + _qmlAppEngine->retranslate(); + } + + emit languageChanged(locale); } void QGCApplication::addCompressedSignal(const QMetaMethod &method) @@ -595,119 +774,155 @@ void QGCApplication::removeCompressedSignal(const QMetaMethod &method) _compressedSignals.remove(method); } -bool QGCApplication::compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) +template +void QGCApplication::throttleSignal(QObject *sender, Func signal, int delayMs) { - if (event->type() != QEvent::MetaCall) { - return QApplication::compressEvent(event, receiver, postedEvents); + static QMap, QTimer*> throttleTimers; + + auto key = qMakePair(sender, reinterpret_cast(signal)); + + if (!throttleTimers.contains(key)) { + QTimer *timer = new QTimer(this); + timer->setSingleShot(true); + timer->setInterval(delayMs); + (void) connect(timer, &QTimer::timeout, [this, sender, signal]() { + (void) std::invoke(signal, sender); + }); + throttleTimers[key] = timer; } - const QMetaCallEvent *mce = static_cast(event); - if (!mce->sender() || !_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) { - return QApplication::compressEvent(event, receiver, postedEvents); + throttleTimers[key]->stop(); + throttleTimers[key]->start(); +} + +bool QGCApplication::eventFilter(QObject *watched, QEvent *event) +{ + if (!event || !watched) { + return QApplication::eventFilter(watched, event); } - for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) { - QPostEvent &cur = *it; - if (cur.receiver != receiver || cur.event == 0 || cur.event->type() != event->type()) { - continue; - } - const QMetaCallEvent *cur_mce = static_cast(cur.event); - if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() || cur_mce->id() != mce->id()) { - continue; - } - /* Keep The Newest Call */ - // We can't merely qSwap the existing posted event with the new one, since QEvent - // keeps track of whether it has been posted. Deletion of a formerly posted event - // takes the posted event list mutex and does a useless search of the posted event - // list upon deletion. We thus clear the QEvent::posted flag before deletion. - struct EventHelper : private QEvent { - static void clearPostedFlag(QEvent * ev) { - (&static_cast(ev)->t)[1] &= ~0x8001; // Hack to clear QEvent::posted - } - }; - EventHelper::clearPostedFlag(cur.event); - delete cur.event; - cur.event = event; - return true; + // For Qt 6.8+, we handle signal compression through event filtering + // instead of the deprecated compressEvent method + if (event->type() == QEvent::MetaCall) { + // For Qt 6.8+, QMetaCallEvent is not accessible + // We need a different strategy - use a timer-based deduplication + // or implement compression at the signal emission point + Q_UNUSED(watched) + Q_UNUSED(event) } - return false; + return QApplication::eventFilter(watched, event); } +// bool QGCApplication::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *ptr) +// { +// if (eventType == "xcb_generic_event_t") { +// xcb_generic_event_t *ev = static_cast(message); +// Q_UNUSED(ev); +// } + +// return QApplication::nativeEventFilter(eventType, message, ptr); +// } + bool QGCApplication::event(QEvent *e) { + if (!e) { + return false; + } + if (e->type() == QEvent::Quit) { - // 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. - // This in turn causes the standard QGC shutdown sequence to not run. So in this case we close the window ourselves such that the - // signal is sent and the normal shutdown sequence runs. - const bool forceClose = _mainRootWindow->property("_forceClose").toBool(); - qCDebug(QGCApplicationLog) << "Quit event" << forceClose; - // forceClose - // true: Standard QGC shutdown sequence is complete. Let the app quit normally by falling through to the base class processing. - // 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. - if (!forceClose) { - // - _mainRootWindow->close(); - e->ignore(); - return true; + // 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. This in turn causes the standard QGC shutdown sequence to not run. + // So in this case we close the window ourselves such that the signal is sent and the normal shutdown sequence runs. + if (_mainRootWindow) { + const bool forceClose = _mainRootWindow->property("_forceClose").toBool(); + qCDebug(QGCApplicationLog) << "Quit event, forceClose:" << forceClose; + + // forceClose + // true: Standard QGC shutdown sequence is complete. Let the app quit normally. + // false: QGC shutdown sequence has not been run yet. Close the main window to kick off shutdown. + if (!forceClose) { + (void) _mainRootWindow->close(); + e->ignore(); + return true; + } } } return QApplication::event(e); } -QGCImageProvider *QGCApplication::qgcImageProvider() +/*===========================================================================*/ + +QGCApplication::CompressedSignalList::CompressedSignalList() { - return dynamic_cast(_qmlAppEngine->imageProvider(_qgcImageProviderId)); + qCDebug(QGCApplicationLog) << this; } -void QGCApplication::shutdown() +QGCApplication::CompressedSignalList::~CompressedSignalList() { - qCDebug(QGCApplicationLog) << "Exit"; + qCDebug(QGCApplicationLog) << this; +} - if (_videoManagerInitialized) { - VideoManager::instance()->cleanup(); +int QGCApplication::CompressedSignalList::_signalIndex(const QMetaMethod &method) +{ + if (method.methodType() != QMetaMethod::Signal) { + qCWarning(QGCApplicationLog) << "Internal error:" << "not a signal" << method.methodType(); + return -1; } - QGCCorePlugin::instance()->cleanup(); + int index = -1; + const QMetaObject * const object = method.enclosingMetaObject(); + for (int i = 0; i <= method.methodIndex(); ++i) { + if (object->method(i).methodType() != QMetaMethod::Signal) { + continue; + } + ++index; + } - // This is bad, but currently qobject inheritances are incorrect and cause crashes on exit without - delete _qmlAppEngine; + return index; } -QString QGCApplication::numberToString(quint64 number) +void QGCApplication::CompressedSignalList::add(const QMetaMethod &method) { - return getCurrentLanguage().toString(number); + const QMetaObject * const object = method.enclosingMetaObject(); + const int signalIndex = _signalIndex(method); + + if ((signalIndex != -1) && object) { + const QMutexLocker locker(&_mutex); + (void) _signalMap[object].insert(signalIndex); + } } -QString QGCApplication::bigSizeToString(quint64 size) +void QGCApplication::CompressedSignalList::remove(const QMetaMethod &method) { - QString result; - const QLocale kLocale = getCurrentLanguage(); - if (size < 1024) { - result = kLocale.toString(size) + "B"; - } else if (size < pow(1024, 2)) { - result = kLocale.toString(static_cast(size) / 1024.0, 'f', 1) + "KB"; - } else if (size < pow(1024, 3)) { - result = kLocale.toString(static_cast(size) / pow(1024, 2), 'f', 1) + "MB"; - } else if (size < pow(1024, 4)) { - result = kLocale.toString(static_cast(size) / pow(1024, 3), 'f', 1) + "GB"; - } else { - result = kLocale.toString(static_cast(size) / pow(1024, 4), 'f', 1) + "TB"; + const int signalIndex = _signalIndex(method); + const QMetaObject * const object = method.enclosingMetaObject(); + + if ((signalIndex != -1) && object) { + const QMutexLocker locker(&_mutex); + auto it = _signalMap.find(object); + if (it != _signalMap.end()) { + (void) it->remove(signalIndex); + if (it->isEmpty()) { + (void) _signalMap.erase(it); + } + } } - return result; } -QString QGCApplication::bigSizeMBToString(quint64 size_MB) +bool QGCApplication::CompressedSignalList::contains(const QMetaObject *metaObject, int signalIndex) const { - QString result; - const QLocale kLocale = getCurrentLanguage(); - if (size_MB < 1024) { - result = kLocale.toString(static_cast(size_MB) , 'f', 0) + " MB"; - } else if(size_MB < pow(1024, 2)) { - result = kLocale.toString(static_cast(size_MB) / 1024.0, 'f', 1) + " GB"; - } else { - result = kLocale.toString(static_cast(size_MB) / pow(1024, 2), 'f', 2) + " TB"; + if (!metaObject) { + return false; } - return result; + + const QMutexLocker locker(&_mutex); + const auto it = _signalMap.constFind(metaObject); + return ((it != _signalMap.constEnd()) && it->contains(signalIndex)); } + +/*===========================================================================*/ + +// setEventDispatcher(QAbstractEventDispatcher) +// installNativeEventFilter(QAbstractNativeEventFilter) diff --git a/src/QGCApplication.h b/src/QGCApplication.h index a656d745e37a..665702f2cdf6 100644 --- a/src/QGCApplication.h +++ b/src/QGCApplication.h @@ -9,12 +9,18 @@ #pragma once -#include +#include +#include + #include +#include #include +#include +#include #include #include #include +#include #include namespace QGCCommandLineParser { @@ -50,12 +56,16 @@ Q_DECLARE_LOGGING_CATEGORY(QGCApplicationLog) class QGCApplication : public QApplication { Q_OBJECT + Q_PROPERTY(QLocale currentLocale READ getCurrentLanguage NOTIFY languageChanged) + Q_PROPERTY(bool runningUnitTests READ runningUnitTests CONSTANT) + Q_PROPERTY(bool fakeMobile READ fakeMobile CONSTANT) /// Unit Test have access to creating and destroying singletons friend class UnitTest; + public: - QGCApplication(int &argc, char *argv[], const QGCCommandLineParser::CommandLineParseResult &args); - ~QGCApplication(); + explicit QGCApplication(int &argc, char *argv[], const QGCCommandLineParser::CommandLineParseResult &args); + ~QGCApplication() override; /// Sets the persistent flag to delete all settings the next time QGroundControl is started. static void deleteAllSettingsNextBoot(); @@ -76,119 +86,176 @@ class QGCApplication : public QApplication /// @return true: Fake ui into showing mobile interface bool fakeMobile() const { return _fakeMobile; } + /// Set application language. Safe to call at startup, may have limitations after threads are created. void setLanguage(); + + /// Get main application window QQuickWindow *mainRootWindow(); + + /// Get milliseconds since application boot uint64_t msecsSinceBoot() const { return _msecsElapsedTime.elapsed(); } - QString numberToString(quint64 number); - QString bigSizeToString(quint64 size); - QString bigSizeMBToString(quint64 size_MB); + + /// Locale-aware number formatting + QString numberToString(quint64 number) const; + QString bigSizeToString(quint64 size) const; + QString bigSizeMBToString(quint64 size_MB) const; /// 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) final; + /// Alternative: Modern signal throttling approach for Qt 6.8+ + template + void throttleSignal(QObject *sender, Func signal, int delayMs = 100); + /// Override event processing for special handling + bool event(QEvent *e) override; + + /// Get cached file paths static QString cachedParameterMetaDataFile(); static QString cachedAirframeMetaDataFile(); -public: /// Perform initialize which is common to both normal application running and unit tests. void init(); + + /// Clean shutdown of the application void shutdown(); /// Although public, these methods are internal and should only be called by UnitTest code QQmlApplicationEngine *qmlAppEngine() const { return _qmlAppEngine; } + /// Get current language + QLocale getCurrentLanguage() const { return _currentLocale; } + + /// Get the QGC image provider + QGCImageProvider *qgcImageProvider(); + + /// Check if application is initialized + bool isInitialized() const { return _appInitialized.load(); } + signals: + /// Emitted when language changes void languageChanged(const QLocale &locale); + /// Emitted when settings are upgraded + void settingsUpgraded(); + public slots: + /// Show vehicle configuration window void showVehicleConfig(); + /// Attempt to close the main window void qmlAttemptWindowClose(); - /// Get current language - QLocale getCurrentLanguage() const { 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 modal application message to the user about the need for a reboot. Multiple messages will be supressed if they occur - /// one after the other. + /// Show modal application message to the user about the need for a reboot. + /// Multiple messages will be suppressed if they occur one after the other. void showRebootAppMessage(const QString &message, const QString &title = QString()); - QGCImageProvider *qgcImageProvider(); - private slots: - /// Called when the delay timer fires to show the missing parameters warning void _missingParamsDisplay(); void _qgcCurrentStableVersionDownloadComplete(const QString &remoteFile, const QString &localFile, const QString &errorMsg); - static bool _parseVersionText(const QString &versionString, int &majorVersion, int &minorVersion, int &buildVersion); void _showDelayedAppMessages(); private: - bool compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) final; + /// Parse version string into QVersionNumber + static QVersionNumber _parseVersionText(const QString &versionString); + /// Event filter for signal compression + bool eventFilter(QObject *watched, QEvent *event) override; + + /// Initialize video subsystem void _initVideo(); /// Initialize the application for normal application boot. Or in other words we are not going to run unit tests. void _initForNormalAppBoot(); + /// Get root QML object QObject *_rootQmlObject(); + + /// Check for newer version availability void _checkForNewVersion(); - bool _runningUnitTests = false; - bool _simpleBootTest = false; - bool _fakeMobile = false; ///< true: Fake ui into displaying mobile interface - bool _logOutput = false; ///< true: Log Qt debug output to file - quint8 _systemId = 0; ///< MAVLink system ID, 0 means not set + // Command line flags (const after construction) + const bool _runningUnitTests; + const bool _simpleBootTest; + const bool _fakeMobile; ///< true: Fake ui into displaying mobile interface + const bool _logOutput; ///< true: Log Qt debug output to file + const quint8 _systemId; ///< MAVLink system ID, 0 means not set + // Missing parameters handling static constexpr 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> _missingParams; ///< List of missing parameter component id:name + mutable QMutex _missingParamsMutex; ///< Protects _missingParams + // Core components QQmlApplicationEngine *_qmlAppEngine = nullptr; + QPointer _mainRootWindow; ///< QPointer for safety + + // Version tracking bool _settingsUpgraded = false; ///< true: Settings format has been upgrade to new version - int _majorVersion = 0; - int _minorVersion = 0; - int _buildVersion = 0; - QQuickWindow *_mainRootWindow = nullptr; - QTranslator _qgcTranslatorSourceCode; ///< translations for source code C++/Qml - QTranslator _qgcTranslatorQtLibs; ///< tranlsations for Qt libraries - QLocale _locale; - bool _error = false; + QVersionNumber _appVersion; ///< Current application version + + // Translation + std::unique_ptr _qgcTranslatorSourceCode; ///< translations for source code C++/Qml + std::unique_ptr _qgcTranslatorQtLibs; ///< translations for Qt libraries + QLocale _currentLocale; ///< Current application locale (needed since we can't safely change default after threads start) + + // State tracking bool _showErrorsInToolbar = false; + std::atomic _appInitialized{false}; ///< Set to true after init() completes (thread-safe) QElapsedTimer _msecsElapsedTime; bool _videoManagerInitialized = false; + // Delayed messages QList> _delayedAppMessages; + mutable QMutex _delayedMessagesMutex; ///< Protects _delayedAppMessages + // Compressed signals handling class CompressedSignalList { public: - CompressedSignalList() {} + CompressedSignalList(); + ~CompressedSignalList(); + void add(const QMetaMethod &method); void remove(const QMetaMethod &method); - bool contains(const QMetaObject *metaObject, int signalIndex); + bool contains(const QMetaObject *metaObject, int signalIndex) const; private: /// Returns a signal index that is can be compared to QMetaCallEvent.signalId static int _signalIndex(const QMetaMethod &method); QMap> _signalMap; + mutable QMutex _mutex; ///< Thread safety Q_DISABLE_COPY(CompressedSignalList) }; CompressedSignalList _compressedSignals; - const QString _settingsVersionKey = QStringLiteral("SettingsVersion"); ///< Settings key which hold settings version - static constexpr const char *_deleteAllSettingsKey = "DeleteAllSettingsNextBoot"; ///< If this settings key is set on boot, all settings will be deleted + // Event compression for Qt 6.8+ compatibility + struct CompressedEventInfo { + QObject *sender; + int signalId; + int methodId; + qint64 timestamp; + }; + + QMap> _pendingCompressedEvents; + mutable QMutex _compressionMutex; + + // Settings keys + static constexpr const char *_settingsVersionKey = "SettingsVersion"; + static constexpr const char *_deleteAllSettingsKey = "DeleteAllSettingsNextBoot"; + static constexpr const char *_qgcImageProviderId = "QGCImages"; - const QString _qgcImageProviderId = QStringLiteral("QGCImages"); + // Prevent copying + Q_DISABLE_COPY(QGCApplication) }; diff --git a/src/Utilities/Platform.cc b/src/Utilities/Platform.cc index ebf110bfd91d..9dd9985a6a5f 100644 --- a/src/Utilities/Platform.cc +++ b/src/Utilities/Platform.cc @@ -9,19 +9,6 @@ #include "Platform.h" -#include -#include - -#include "QGCCommandLineParser.h" - -#ifdef Q_OS_ANDROID - #include "AndroidInterface.h" -#endif - -#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID) - #include "SignalHandler.h" -#endif - #if defined(Q_OS_MACOS) #include #elif defined(Q_OS_WIN) @@ -36,6 +23,34 @@ #endif #endif +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + #include + #include +#if !defined(QGC_NO_SERIAL_LINK) + #include + #include + #include +#endif +#endif + +#include +#include +#include +#include + +#ifdef Q_OS_ANDROID + #include "AndroidInterface.h" +#endif + +#include "QGCCommandLineParser.h" +#include "QGCLoggingCategory.h" + +#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID) + #include "SignalHandler.h" +#endif + +QGC_LOGGING_CATEGORY(PlatformLog, "qgc.utilities.platform") + namespace { #if defined(Q_OS_MACOS) @@ -52,6 +67,8 @@ void disableAppNapViaInfoDict() } #endif // Q_OS_MACOS +/*---------------------------------------------------------------------------*/ + #if defined(Q_OS_WIN) #if defined(_MSC_VER) @@ -132,7 +149,264 @@ void setWindowsErrorModes(bool quietWindowsAsserts) } // namespace -void Platform::setupPreApp(const QGCCommandLineParser::CommandLineParseResult &cli) +/*---------------------------------------------------------------------------*/ + +namespace Platform { + +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) +bool isUserRoot() +{ + return (::getuid() == 0); +} + +#if !defined(QGC_NO_SERIAL_LINK) +void checkSerialPortPermissions(QString &errorMessage) +{ + // Method 1: Check actual group membership using system calls + bool hasDialoutAccess = false; + bool hasUucp = false; + bool hasTty = false; + + // Get the current user's effective UID + const uid_t uid = getuid(); + + // Get user info + const struct passwd *pw = getpwuid(uid); + if (!pw) { + qCWarning(PlatformLog) << "Cannot determine current user"; + return; + } + + const QString username = QString::fromLocal8Bit(pw->pw_name); + const gid_t primary_gid = pw->pw_gid; + + // Get number of supplementary groups + const int ngroups = getgroups(0, nullptr); + if (ngroups > 0) { + QList groups(ngroups); + if (getgroups(ngroups, groups.data()) != -1) { + // Add primary group to the list + groups.append(primary_gid); + + // Check each group + for (const gid_t gid : groups) { + const struct group *grp = getgrgid(gid); + if (grp) { + const QString groupName = QString::fromLocal8Bit(grp->gr_name); + if (groupName == QStringLiteral("dialout")) { + hasDialoutAccess = true; + } else if (groupName == QStringLiteral("uucp")) { + hasUucp = true; + } else if (groupName == QStringLiteral("tty")) { + hasTty = true; + } + } + } + } + } + + // Method 2: Also check by trying to access a serial port + bool canAccessSerialDevices = false; + const QDir devDir(QStringLiteral("/dev")); + + // Check common serial device patterns + const QStringList serialPatterns = { + QStringLiteral("ttyUSB*"), + QStringLiteral("ttyACM*"), + QStringLiteral("ttyS*"), + QStringLiteral("rfcomm*") + }; + + for (const QString &pattern : serialPatterns) { + const QStringList devices = devDir.entryList(QStringList() << pattern, QDir::System); + for (const QString &device : devices) { + const QString devicePath = QStringLiteral("/dev/") + device; + const QFileInfo deviceInfo(devicePath); + + // Check if it's a character device (serial ports are character devices) + if (!deviceInfo.exists()) { + continue; + } + + // On Linux, we can check if it's a character device by checking the file type + struct stat st; + if (stat(devicePath.toLocal8Bit().constData(), &st) == 0) { + if (!S_ISCHR(st.st_mode)) { + continue; // Not a character device + } + } + + // Safer and fast: test permission bits only + if (deviceInfo.isReadable() && deviceInfo.isWritable()) { + canAccessSerialDevices = true; + break; + } else { + qCDebug(PlatformLog) << "Cannot access serial device:" << devicePath + << "Permissions:" << deviceInfo.permissions() + << "Owner:" << deviceInfo.owner() + << "Group:" << deviceInfo.group(); + } + } + + if (canAccessSerialDevices) { + break; + } + } + + // Method 3: Check ModemManager interference + bool modemManagerRunning = false; + QProcess mmCheck; + mmCheck.start("systemctl", QStringList() << "is-active" << "ModemManager"); + if (mmCheck.waitForFinished(1000)) { + const QString output = mmCheck.readAllStandardOutput().trimmed(); + modemManagerRunning = (output == QStringLiteral("active")); + } else { + // Fallback: check if process is running + QProcess psCheck; + psCheck.start("pgrep", QStringList() << "ModemManager"); + if (psCheck.waitForFinished(1000)) { + modemManagerRunning = !psCheck.readAllStandardOutput().isEmpty(); + } + } + + // Build list of missing groups and check which groups exist on system + QStringList neededGroups; + bool hasDialoutGroup = false; + bool hasUucpGroup = false; + + if (!hasDialoutAccess && !hasUucp && !hasTty) { + // User has no serial access groups at all + + // Check which groups exist on the system + QFile groupFile(QStringLiteral("/etc/group")); + if (groupFile.open(QIODevice::ReadOnly)) { + QTextStream stream(&groupFile); + QString line; + + while (stream.readLineInto(&line)) { + if (line.startsWith(QStringLiteral("dialout:"))) { + hasDialoutGroup = true; + } else if (line.startsWith(QStringLiteral("uucp:"))) { + hasUucpGroup = true; + } + } + groupFile.close(); + } + + // Prefer dialout if it exists, otherwise use uucp + if (hasDialoutGroup) { + neededGroups << QStringLiteral("dialout"); + } else if (hasUucpGroup) { + neededGroups << QStringLiteral("uucp"); + } else { + // Default to dialout if neither exists (shouldn't happen) + neededGroups << QStringLiteral("dialout"); + } + } + + // Determine if we need to show a warning + QString warningMessage; + + if (!canAccessSerialDevices && !neededGroups.isEmpty()) { + // Build distribution-specific instructions + QString distroName = QStringLiteral("Linux"); + + // Try to determine distribution + QFile osRelease("/etc/os-release"); + if (osRelease.open(QIODevice::ReadOnly)) { + QTextStream stream(&osRelease); + QString line; + while (stream.readLineInto(&line)) { + if (line.startsWith("NAME=")) { + distroName = line.mid(5).remove('"').trimmed(); + break; + } + } + osRelease.close(); + } + + QString instructions; + // Build instructions based on distribution + if (distroName.contains(QStringLiteral("Ubuntu")) || distroName.contains(QStringLiteral("Debian"))) { + instructions = QStringLiteral( + "If you are using %1, execute the following commands to fix these issues:
" + "
sudo usermod -a -G %2 $USER
" + ).arg(distroName, neededGroups.constFirst()); + + if (modemManagerRunning) { + instructions += QStringLiteral("sudo apt-get remove modemmanager
"); + } + + } else if (distroName.contains(QStringLiteral("Fedora")) || distroName.contains(QStringLiteral("Red Hat")) || distroName.contains(QStringLiteral("CentOS"))) { + instructions = QStringLiteral( + "If you are using %1, execute the following commands to fix these issues:
" + "
sudo usermod -a -G %2 $USER
" + ).arg(distroName, neededGroups.constFirst()); + + if (modemManagerRunning) { + instructions += QStringLiteral("sudo systemctl disable ModemManager
" + "sudo systemctl stop ModemManager
"); + } + + } else if (distroName.contains(QStringLiteral("Arch")) || distroName.contains(QStringLiteral("Manjaro"))) { + const QString groupName = hasUucpGroup ? QStringLiteral("uucp") : QStringLiteral("dialout"); + instructions = QStringLiteral( + "If you are using %1, execute the following commands to fix these issues:
" + "
sudo usermod -a -G %2 $USER
" + ).arg(distroName, groupName); + + if (modemManagerRunning) { + instructions += QStringLiteral("sudo systemctl disable ModemManager
" + "sudo systemctl stop ModemManager
"); + } + + } else { + // Generic instructions + instructions = QStringLiteral( + "To fix these issues:
" + "
sudo usermod -a -G %1 $USER
") + .arg(neededGroups.constFirst()); + + if (modemManagerRunning) { + instructions += QStringLiteral("# Disable ModemManager (command varies by distribution)
"); + } + } + + instructions += QStringLiteral("# Then log out and log back in for changes to take effect
"); + + warningMessage = QCoreApplication::translate("platform", + "The current user (%1) does not have the correct permissions to access serial devices. " + "You need to be a member of the '%2' group.

" + ).arg(username, neededGroups.constFirst()); + + if (modemManagerRunning) { + warningMessage += QCoreApplication::translate("platform", "Additionally, ModemManager is running and may interfere with serial devices.

"); + } + + warningMessage += instructions; + + } else if (modemManagerRunning && !canAccessSerialDevices) { + // User has permissions but ModemManager might be interfering + warningMessage = QCoreApplication::translate("platform", + "ModemManager is running and may interfere with serial device access. " + "Consider disabling it if you experience connection issues.

" + "To disable ModemManager:
" + "
sudo systemctl disable ModemManager
" + "sudo systemctl stop ModemManager
"); + } + + if (warningMessage.isEmpty() && !canAccessSerialDevices) { + // User has group membership but still can't access devices + // This might be because no devices are connected, which is fine + qCDebug(PlatformLog) << "User" << username << "has serial group membership but no accessible devices found"; + } +} +#endif // !defined(QGC_NO_SERIAL_LINK) +#endif // defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + +/*---------------------------------------------------------------------------*/ + +void setupPreApp(const QGCCommandLineParser::CommandLineParseResult &cli) { #ifdef Q_OS_UNIX if (!qEnvironmentVariableIsSet("QT_ASSUME_STDERR_HAS_CONSOLE")) { @@ -164,10 +438,11 @@ void Platform::setupPreApp(const QGCCommandLineParser::CommandLineParseResult &c } QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); QCoreApplication::setAttribute(Qt::AA_CompressTabletEvents); } -void Platform::setupPostApp() +void setupPostApp() { #if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID) SignalHandler* signalHandler = new SignalHandler(QCoreApplication::instance()); @@ -178,3 +453,5 @@ void Platform::setupPostApp() AndroidInterface::checkStoragePermissions(); #endif } + +} // namespace Platform diff --git a/src/Utilities/Platform.h b/src/Utilities/Platform.h index 516931f894b6..b58879f5b040 100644 --- a/src/Utilities/Platform.h +++ b/src/Utilities/Platform.h @@ -9,12 +9,25 @@ #pragma once +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(PlatformLog) + namespace QGCCommandLineParser { struct CommandLineParseResult; } namespace Platform { +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) +bool isUserRoot(); +#if !defined(QGC_NO_SERIAL_LINK) +void checkSerialPortPermissions(QString &errorMessage); +#endif +#endif + // Call before constructing Q(Core)Application. void setupPreApp(const QGCCommandLineParser::CommandLineParseResult &cli); diff --git a/src/Vehicle/Vehicle.h b/src/Vehicle/Vehicle.h index 1a70b7637d2c..a2586b3d4b51 100644 --- a/src/Vehicle/Vehicle.h +++ b/src/Vehicle/Vehicle.h @@ -119,8 +119,8 @@ class Vehicle : public VehicleFactGroup QObject* parent = nullptr); // Pass these into the offline constructor to create an offline vehicle which tracks the offline vehicle settings - static const MAV_AUTOPILOT MAV_AUTOPILOT_TRACK = static_cast(-1); - static const MAV_TYPE MAV_TYPE_TRACK = static_cast(-1); + static constexpr MAV_AUTOPILOT MAV_AUTOPILOT_TRACK = MAV_AUTOPILOT_ENUM_END; + static constexpr MAV_TYPE MAV_TYPE_TRACK = MAV_TYPE_ENUM_END; // The following is used to create a disconnected Vehicle for use while offline editing. Vehicle(MAV_AUTOPILOT firmwareType, diff --git a/src/main.cc b/src/main.cc index e29c7000426d..1b4704da8b87 100644 --- a/src/main.cc +++ b/src/main.cc @@ -20,11 +20,6 @@ #include "RunGuard.h" #endif -#ifdef Q_OS_LINUX - #include - #include -#endif - #ifdef QGC_UNITTEST_BUILD #include "UnitTestList.h" #endif @@ -32,7 +27,7 @@ int main(int argc, char *argv[]) { #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) - if (::getuid() == 0) { + if (Platform::isUserRoot()) { const QApplication errorApp(argc, argv); // QErrorMessage (void) QMessageBox::critical(nullptr,