diff --git a/src/QGCApplication.h b/src/QGCApplication.h index e4cd6ddf0b33..3eb8a68afac4 100644 --- a/src/QGCApplication.h +++ b/src/QGCApplication.h @@ -9,12 +9,12 @@ #pragma once +#include #include #include #include #include #include - #include class QQmlApplicationEngine; @@ -38,6 +38,8 @@ class QMetaObject; #define qgcApp() qApp +Q_DECLARE_LOGGING_CATEGORY(QGCApplicationLog) + /// The main application and management class. /// Needs QApplication base to support QtCharts module. /// TODO: Use QtGraphs to convert to QGuiApplication @@ -129,7 +131,7 @@ private slots: bool compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) final; void _initVideo(); - + /// Initialize the application for normal application boot. Or in other words we are not going to run unit tests. void _initForNormalAppBoot(); @@ -137,7 +139,8 @@ private slots: void _checkForNewVersion(); bool _runningUnitTests = false; - bool _simpleBootTest = false; + bool _simpleBootTest = false; + 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 diff --git a/src/RunGuard.h b/src/RunGuard.h index b35414a90f7d..0f3546286f4a 100644 --- a/src/RunGuard.h +++ b/src/RunGuard.h @@ -36,4 +36,6 @@ class RunGuard const QString _key; const QString _lockFilePath; QLockFile _lockFile; + + Q_DISABLE_COPY(RunGuard) }; diff --git a/src/Utilities/CMakeLists.txt b/src/Utilities/CMakeLists.txt index ae24b3454efd..0af6035965c6 100644 --- a/src/Utilities/CMakeLists.txt +++ b/src/Utilities/CMakeLists.txt @@ -22,13 +22,15 @@ target_sources(${CMAKE_PROJECT_NAME} StateMachine.h ) -if(LINUX) +if(NOT ANDROID AND NOT IOS) target_sources(${CMAKE_PROJECT_NAME} PRIVATE SignalHandler.cc SignalHandler.h ) -elseif(IOS) +endif() + +if(IOS) target_sources(${CMAKE_PROJECT_NAME} PRIVATE MobileScreenMgr.mm diff --git a/src/Utilities/Platform.cc b/src/Utilities/Platform.cc index 2181743e1cb2..f16a04690072 100644 --- a/src/Utilities/Platform.cc +++ b/src/Utilities/Platform.cc @@ -1,22 +1,158 @@ +/**************************************************************************** + * + * (c) 2009-2024 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + #include "Platform.h" -#ifdef Q_OS_MAC -#include +#include +#include + +#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID) +#include "SignalHandler.h" +#endif -void Platform::disableAppNapViaInfoDict() +#if defined(Q_OS_MACOS) + #include +#elif defined(Q_OS_WIN) + #include + #include + #include // std::size + #include // swprintf + #if defined(_MSC_VER) + #include + #include + #include // _snwprintf_s + #endif +#endif + +namespace { + +#if defined(Q_OS_MACOS) +void disableAppNapViaInfoDict() { CFBundleRef bundle = CFBundleGetMainBundle(); - if (!bundle) return; + if (!bundle) { + return; + } + CFMutableDictionaryRef infoDict = const_cast(CFBundleGetInfoDictionary(bundle)); + if (infoDict) { + CFDictionarySetValue(infoDict, CFSTR("NSAppSleepDisabled"), kCFBooleanTrue); + } +} +#endif // Q_OS_MACOS - // CFBundleGetInfoDictionary returns the dictionary the OS already - // parsed from Info.plist. Cast it to mutable so we can tweak it. - CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(bundle); +#if defined(Q_OS_WIN) - // Inject the key → true - if (infoDict) { - CFDictionarySetValue(infoDict, - CFSTR("NSAppSleepDisabled"), - kCFBooleanTrue); +#if defined(_MSC_VER) +int __cdecl WindowsCrtReportHook(int reportType, char* message, int* returnValue) +{ + if (message) { + std::cerr << message << std::endl; + } + if (reportType == _CRT_ASSERT) { + if (returnValue) { + *returnValue = 0; + } + return 1; // handled + } + return 0; // let CRT continue +} + +void __cdecl WindowsPurecallHandler() +{ + (void) OutputDebugStringW(L"QGC: _purecall\n"); +} + +void WindowsInvalidParameterHandler([[maybe_unused]] const wchar_t* expression, + [[maybe_unused]] const wchar_t* function, + [[maybe_unused]] const wchar_t* file, + [[maybe_unused]] unsigned int line, + [[maybe_unused]] uintptr_t pReserved) +{ + +} +#endif // _MSC_VER + +LPTOP_LEVEL_EXCEPTION_FILTER g_prevUef = nullptr; + +LONG WINAPI WindowsUnhandledExceptionFilter(EXCEPTION_POINTERS* ep) +{ + const DWORD code = (ep && ep->ExceptionRecord) ? ep->ExceptionRecord->ExceptionCode : 0; + wchar_t buf[128] = {}; +#if defined(_MSC_VER) + (void) _snwprintf_s(buf, _TRUNCATE, L"QGC: unhandled SEH 0x%08lX\n", static_cast(code)); +#else + (void) swprintf(buf, static_cast(std::size(buf)), L"QGC: unhandled SEH 0x%08lX\n", static_cast(code)); +#endif + (void) OutputDebugStringW(buf); + + const HANDLE h = GetStdHandle(STD_ERROR_HANDLE); + if (h && (h != INVALID_HANDLE_VALUE)) { + DWORD ignored = 0; + const char narrow[] = "QGC: unhandled SEH\n"; + (void) WriteFile(h, narrow, (DWORD)sizeof(narrow) - 1, &ignored, nullptr); } + + return EXCEPTION_EXECUTE_HANDLER; } + +void setWindowsErrorModes(bool quietWindowsAsserts) +{ + (void) SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); + g_prevUef = SetUnhandledExceptionFilter(WindowsUnhandledExceptionFilter); + +#if defined(_MSC_VER) + (void) _set_invalid_parameter_handler(WindowsInvalidParameterHandler); + (void) _set_purecall_handler(WindowsPurecallHandler); + + if (quietWindowsAsserts) { + (void) _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); + (void) _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); + (void) _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); + (void) _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, WindowsCrtReportHook); + (void) _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + (void) _set_error_mode(_OUT_TO_STDERR); + } +#else + Q_UNUSED(quietWindowsAsserts); #endif +} +#endif // Q_OS_WIN + +} // namespace + +void Platform::setupPreApp(bool quietWindowsAsserts) +{ +#ifdef Q_OS_UNIX + if (!qEnvironmentVariableIsSet("QT_ASSUME_STDERR_HAS_CONSOLE")) { + (void) qputenv("QT_ASSUME_STDERR_HAS_CONSOLE", "1"); + } + if (!qEnvironmentVariableIsSet("QT_FORCE_STDERR_LOGGING")) { + (void) qputenv("QT_FORCE_STDERR_LOGGING", "1"); + } +#endif + +#ifdef Q_OS_WIN + if (!qEnvironmentVariableIsSet("QT_WIN_DEBUG_CONSOLE")) { + (void) qputenv("QT_WIN_DEBUG_CONSOLE", "attach"); + } + setWindowsErrorModes(quietWindowsAsserts); +#endif + +#ifdef Q_OS_MACOS + disableAppNapViaInfoDict(); +#endif +} + +void Platform::setupPostApp() +{ +#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID) + SignalHandler* signalHandler = new SignalHandler(QCoreApplication::instance()); + (void) signalHandler->setupSignalHandlers(); +#endif +} diff --git a/src/Utilities/Platform.h b/src/Utilities/Platform.h index 09073af0312b..fe9e1ec37367 100644 --- a/src/Utilities/Platform.h +++ b/src/Utilities/Platform.h @@ -1,11 +1,20 @@ -#pragma once +/**************************************************************************** + * + * (c) 2009-2024 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ -#include +#pragma once namespace Platform { -#ifdef Q_OS_MAC - /// Prevent Apple's app nap from screwing us over - /// tip: the domain can be cross-checked on the command line with - void disableAppNapViaInfoDict(); -#endif -} + +// Call before constructing Q(Core)Application. +void setupPreApp(bool quietWindowsAsserts); + +// Call after Q(Core)Application exists and logging is installed. +void setupPostApp(); + +} // namespace Platform diff --git a/src/Utilities/QGCLogging.cc b/src/Utilities/QGCLogging.cc index cbe499eab503..97ce934a3ad9 100644 --- a/src/Utilities/QGCLogging.cc +++ b/src/Utilities/QGCLogging.cc @@ -20,25 +20,6 @@ QGC_LOGGING_CATEGORY(QGCLoggingLog, "qgc.utilities.qgclogging") -#ifdef Q_OS_WIN - -#include -#include -#include - -/// CRT Report Hook installed using _CrtSetReportHook. We install this hook when -/// we don't want asserts to pop a dialog on windows. -static int WindowsCrtReportHook(int reportType, char *message, int *returnValue) -{ - Q_UNUSED(reportType); - - std::cerr << message << std::endl; // Output message to stderr - *returnValue = 0; // Don't break into debugger - return true; // We handled this fully ourselves -} - -#endif - Q_GLOBAL_STATIC(QGCLogging, _qgcLogging) static QtMessageHandler defaultHandler = nullptr; @@ -88,16 +69,8 @@ QGCLogging::~QGCLogging() qCDebug(QGCLoggingLog) << this; } -void QGCLogging::installHandler(bool quietWindowsAsserts) +void QGCLogging::installHandler() { -#ifdef Q_OS_WIN - if (quietWindowsAsserts) { - _CrtSetReportHook(WindowsCrtReportHook); - } -#else - Q_UNUSED(quietWindowsAsserts) -#endif - // Define the format for qDebug/qWarning/etc output qSetMessagePattern(QStringLiteral("%{category}:: %{time process} - %{type}: %{message} (%{function}:%{line})")); diff --git a/src/Utilities/QGCLogging.h b/src/Utilities/QGCLogging.h index eab624d0a25f..746712aac58c 100644 --- a/src/Utilities/QGCLogging.h +++ b/src/Utilities/QGCLogging.h @@ -28,7 +28,7 @@ class QGCLogging : public QStringListModel static QGCLogging *instance(); /// Install Qt message handler to route logs through this class - static void installHandler(bool quietWindowsAsserts); + static void installHandler(); /// Write current log messages to a file asynchronously Q_INVOKABLE void writeMessages(const QString &destFile); diff --git a/src/Utilities/SignalHandler.cc b/src/Utilities/SignalHandler.cc index cc378b1ed433..3a24b5a1e9a0 100644 --- a/src/Utilities/SignalHandler.cc +++ b/src/Utilities/SignalHandler.cc @@ -8,119 +8,247 @@ ****************************************************************************/ #include "SignalHandler.h" -#include "QGCApplication.h" -#include "QGCLoggingCategory.h" -#include -#include +#include #include -#include -#include +#include "QGCApplication.h" +#include "QGCLoggingCategory.h" QGC_LOGGING_CATEGORY(SignalHandlerLog, "qgc.utilities.signalhandler") -int SignalHandler::sigIntFd[2] = {0, 0}; -int SignalHandler::sigTermFd[2] = {0, 0}; - -Q_GLOBAL_STATIC(SignalHandler, _signalHandlerInstance); +std::atomic SignalHandler::s_current{nullptr}; -SignalHandler *SignalHandler::instance() +SignalHandler::SignalHandler(QObject* parent) + : QObject(parent) { - return _signalHandlerInstance(); + s_current.store(this, std::memory_order_release); + qCDebug(SignalHandlerLog) << this; } -SignalHandler::SignalHandler(QObject *parent) - : QObject(parent) +void SignalHandler::_handleExitEvent() { - if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigIntFd)) { - qCFatal(SignalHandlerLog) << "Couldn't create INT socketpair"; - } - _notifierInt = new QSocketNotifier(sigIntFd[1], QSocketNotifier::Read, this); - (void) connect(_notifierInt, &QSocketNotifier::activated, this, &SignalHandler::_onSigInt); - - if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigTermFd)) { - qCFatal(SignalHandlerLog) << "Couldn't create TERM socketpair"; + if (qgcApp() && qgcApp()->mainRootWindow()) { + (void) qgcApp()->mainRootWindow()->close(); + QEvent ev(QEvent::Quit); + (void) qgcApp()->event(&ev); } - _notifierTerm = new QSocketNotifier(sigTermFd[1], QSocketNotifier::Read, this); - (void) connect(_notifierTerm, &QSocketNotifier::activated, this, &SignalHandler::_onSigTerm); } -void SignalHandler::_onSigInt() -{ - _notifierInt->setEnabled(false); - char b; - (void) ::read(sigIntFd[1], &b, sizeof(b)); +#ifdef Q_OS_WIN - _sigIntCount++; +#include +#include - if (_sigIntCount == 1) { - qCDebug(SignalHandlerLog) << "Caught SIGINT—press Ctrl+C again to exit immediately"; +// Plain C thunk with Windows signature. Delegates to class static. +static BOOL WINAPI _consoleCtrlHandler(DWORD evt) +{ + return SignalHandler::consoleCtrlHandler(static_cast(evt)) ? TRUE : FALSE; +} - if (qgcApp() && qgcApp()->mainRootWindow()) { - (void) qgcApp()->mainRootWindow()->close(); - QEvent ev(QEvent::Quit); - (void) qgcApp()->event(&ev); +int SignalHandler::consoleCtrlHandler(unsigned long evt) +{ + switch (evt) { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: { + SignalHandler* self = SignalHandler::current(); + if (!self || !self->_signalEvent || (self->_signalEvent == reinterpret_cast(INVALID_HANDLE_VALUE))) { + return 0; } - } else { - qCDebug(SignalHandlerLog) << "Caught second SIGINT—exiting immediately"; - _exit(0); + (void) SetEvent(static_cast(self->_signalEvent)); // non-blocking + return 1; + } + default: + return 0; } - - _notifierInt->setEnabled(true); } -void SignalHandler::_onSigTerm() +SignalHandler::~SignalHandler() { - _notifierTerm->setEnabled(false); - char b; - (void) ::read(sigTermFd[1], &b, sizeof(b)); + if (_notifier) { + _notifier->setEnabled(false); + } - qCDebug(SignalHandlerLog) << "Caught SIGTERM—shutting down gracefully"; - if (qgcApp() && qgcApp()->mainRootWindow()) { - (void) qgcApp()->mainRootWindow()->close(); - QEvent ev(QEvent::Quit); - (void) qgcApp()->event(&ev); + if (_signalEvent && (_signalEvent != reinterpret_cast(INVALID_HANDLE_VALUE))) { + (void) CloseHandle(static_cast(_signalEvent)); + _signalEvent = reinterpret_cast(INVALID_HANDLE_VALUE); } - _notifierTerm->setEnabled(true); + (void) SetConsoleCtrlHandler(&_consoleCtrlHandler, FALSE); + s_current.store(nullptr, std::memory_order_release); + + qCDebug(SignalHandlerLog) << this; } -void SignalHandler::intSignalHandler(int signum) +int SignalHandler::setupSignalHandlers() { - Q_ASSERT(signum == SIGINT); + // Create auto-reset event. Initial state = non-signaled. + _signalEvent = reinterpret_cast(CreateEventW(/*lpEventAttributes*/ nullptr, /*bManualReset*/ FALSE, /*bInitialState*/ FALSE, /*lpName*/ nullptr)); + if (!_signalEvent || (_signalEvent == reinterpret_cast(INVALID_HANDLE_VALUE))) { + return 1; + } + + _notifier = new QWinEventNotifier(static_cast(_signalEvent), this); + (void) connect(_notifier, &QWinEventNotifier::activated, this, [this]([[maybe_unused]] HANDLE handle) { + // Auto-reset event already consumed. No drain needed. + if (_sigIntCount.fetch_add(1, std::memory_order_relaxed) == 0) { + qCDebug(SignalHandlerLog) << "Console event—press Ctrl+C again to exit immediately"; + _handleExitEvent(); + } else { + qCDebug(SignalHandlerLog) << "Caught second SIGINT—exiting immediately"; + _exit(0); + } + }); + + if (!SetConsoleCtrlHandler(&_consoleCtrlHandler, TRUE)) { + return 2; + } - char b = 1; - (void) ::write(sigIntFd[0], &b, sizeof(b)); + return 0; } -void SignalHandler::termSignalHandler(int signum) +#else // POSIX + +#include +#include +#include +#include +#include +#include + +#include + +SignalHandler::~SignalHandler() { - Q_ASSERT(signum == SIGTERM); + struct sigaction sa{}; + sa.sa_handler = SIG_DFL; + (void) sigaction(SIGINT, &sa, nullptr); + (void) sigaction(SIGTERM, &sa, nullptr); - char b = 1; - (void) ::write(sigTermFd[0], &b, sizeof(b)); + if (_notifierInt) { + _notifierInt->setEnabled(false); + } + if (_notifierTerm) { + _notifierTerm->setEnabled(false); + } + + if (_sigIntFd[0] >= 0) { + (void) ::close(_sigIntFd[0]); + _sigIntFd[0] = -1; + } + if (_sigIntFd[1] >= 0) { + (void) ::close(_sigIntFd[1]); + _sigIntFd[1] = -1; + } + if (_sigTermFd[0] >= 0) { + (void) ::close(_sigTermFd[0]); + _sigTermFd[0] = -1; + } + if (_sigTermFd[1] >= 0) { + (void) ::close(_sigTermFd[1]); + _sigTermFd[1] = -1; + } + + s_current.store(nullptr, std::memory_order_release); + qCDebug(SignalHandlerLog) << this; } int SignalHandler::setupSignalHandlers() { + // Datagram socketpairs: read end = [0], write end = [1] + if (::socketpair(AF_UNIX, SOCK_DGRAM, 0, _sigIntFd)) { + qCCritical(SignalHandlerLog) << "Failed to create SIGINT socketpair:" << strerror(errno); + return 1; + } + if (::socketpair(AF_UNIX, SOCK_DGRAM, 0, _sigTermFd)) { + qCCritical(SignalHandlerLog) << "Failed to create SIGTERM socketpair:" << strerror(errno); + return 2; + } + + // Close-on-exec to prevent fd leaks into children + (void) fcntl(_sigIntFd[0], F_SETFD, fcntl(_sigIntFd[0], F_GETFD, 0) | FD_CLOEXEC); + (void) fcntl(_sigIntFd[1], F_SETFD, fcntl(_sigIntFd[1], F_GETFD, 0) | FD_CLOEXEC); + (void) fcntl(_sigTermFd[0], F_SETFD, fcntl(_sigTermFd[0], F_GETFD, 0) | FD_CLOEXEC); + (void) fcntl(_sigTermFd[1], F_SETFD, fcntl(_sigTermFd[1], F_GETFD, 0) | FD_CLOEXEC); + + // Non-blocking read ends + (void) fcntl(_sigIntFd[0], F_SETFL, fcntl(_sigIntFd[0], F_GETFL, 0) | O_NONBLOCK); + (void) fcntl(_sigTermFd[0], F_SETFL, fcntl(_sigTermFd[0], F_GETFL, 0) | O_NONBLOCK); + + _notifierInt = new QSocketNotifier(_sigIntFd[0], QSocketNotifier::Read, this); + _notifierTerm = new QSocketNotifier(_sigTermFd[0], QSocketNotifier::Read, this); + (void) connect(_notifierInt, &QSocketNotifier::activated, this, &SignalHandler::_onSigInt); + (void) connect(_notifierTerm, &QSocketNotifier::activated, this, &SignalHandler::_onSigTerm); + struct sigaction sa_int{}; - sa_int.sa_handler = SignalHandler::intSignalHandler; - (void) sigemptyset(&sa_int.sa_mask); + sigemptyset(&sa_int.sa_mask); sa_int.sa_flags = 0; - sa_int.sa_flags |= SA_RESTART; + sa_int.sa_handler = SignalHandler::_intSignalHandler; if (sigaction(SIGINT, &sa_int, nullptr)) { - return 1; + return 3; } struct sigaction sa_term{}; - sa_term.sa_handler = SignalHandler::termSignalHandler; - (void) sigemptyset(&sa_term.sa_mask); + sigemptyset(&sa_term.sa_mask); sa_term.sa_flags = 0; - sa_term.sa_flags |= SA_RESTART; + sa_term.sa_handler = SignalHandler::_termSignalHandler; if (sigaction(SIGTERM, &sa_term, nullptr)) { - return 2; + return 4; } return 0; } + +void SignalHandler::_onSigInt() +{ + char b; + (void) ::read(_sigIntFd[0], &b, 1); // single-byte drain + + if (_sigIntCount.fetch_add(1, std::memory_order_relaxed) == 0) { + qCDebug(SignalHandlerLog) << "Caught SIGINT—press Ctrl+C again to exit immediately"; + _handleExitEvent(); + } else { + qCDebug(SignalHandlerLog) << "Caught second SIGINT—exiting immediately"; + _exit(0); + } +} + +void SignalHandler::_onSigTerm() +{ + char b; + (void) ::read(_sigTermFd[0], &b, 1); // single-byte drain + + qCDebug(SignalHandlerLog) << "Caught SIGTERM—shutting down gracefully"; + _handleExitEvent(); +} + +void SignalHandler::_intSignalHandler(int signum) +{ + if (signum != SIGINT) { + return; + } + const SignalHandler* self = current(); + if (!self) { + return; + } + const char b = 1; + [[maybe_unused]] const ssize_t n = ::write(self->_sigIntFd[1], &b, sizeof(b)); // no logging from signal handler +} + +void SignalHandler::_termSignalHandler(int signum) +{ + if (signum != SIGTERM) { + return; + } + const SignalHandler* self = current(); + if (!self) { + return; + } + const char b = 1; + [[maybe_unused]] const ssize_t n = ::write(self->_sigTermFd[1], &b, sizeof(b)); // no logging from signal handler +} + +#endif // POSIX diff --git a/src/Utilities/SignalHandler.h b/src/Utilities/SignalHandler.h index 9a2f84529947..3ffe3ca1b26c 100644 --- a/src/Utilities/SignalHandler.h +++ b/src/Utilities/SignalHandler.h @@ -9,36 +9,56 @@ #pragma once +#include + #include #include +#include // Qt::HANDLE -class QSocketNotifier; +#ifdef Q_OS_WIN + class QWinEventNotifier; +#else + class QSocketNotifier; +#endif Q_DECLARE_LOGGING_CATEGORY(SignalHandlerLog) -class SignalHandler : public QObject +class SignalHandler final : public QObject { Q_OBJECT public: - explicit SignalHandler(QObject *parent = nullptr); + explicit SignalHandler(QObject* parent = nullptr); + ~SignalHandler() override; - static SignalHandler *instance(); + int setupSignalHandlers(); - static int setupSignalHandlers(); - static void intSignalHandler(int signum); - static void termSignalHandler(int signum); + static SignalHandler* current() { return s_current.load(std::memory_order_acquire); } +#ifdef Q_OS_WIN + static int consoleCtrlHandler(unsigned long evt); +#else private slots: void _onSigInt(); void _onSigTerm(); +#endif private: - static int sigIntFd[2]; - static int sigTermFd[2]; + void _handleExitEvent(); + +#ifdef Q_OS_WIN + QWinEventNotifier* _notifier{nullptr}; + Qt::HANDLE _signalEvent{nullptr}; +#else + static void _intSignalHandler(int signum); + static void _termSignalHandler(int signum); - QSocketNotifier *_notifierInt = nullptr; - QSocketNotifier *_notifierTerm = nullptr; + int _sigIntFd[2] = {-1, -1}; // socketpair read=0, write=1 + int _sigTermFd[2] = {-1, -1}; + QSocketNotifier* _notifierInt{nullptr}; + QSocketNotifier* _notifierTerm{nullptr}; +#endif - int _sigIntCount = 0; + std::atomic _sigIntCount{0}; + static std::atomic s_current; }; diff --git a/src/main.cc b/src/main.cc index 00662144ebce..39f1b064d201 100644 --- a/src/main.cc +++ b/src/main.cc @@ -7,6 +7,7 @@ * ****************************************************************************/ +#include // std::size #include #include @@ -17,48 +18,34 @@ #include "MavlinkSettings.h" #include "Platform.h" -#ifdef Q_OS_WIN - #include -#endif - #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #include #include "RunGuard.h" #endif -#ifdef Q_OS_LINUX #ifdef Q_OS_ANDROID #include "AndroidInterface.h" -#else +#endif + +#ifdef Q_OS_LINUX #include #include - #include "SignalHandler.h" -#endif #endif #ifdef QGC_UNITTEST_BUILD #include "UnitTestList.h" #endif -//----------------------------------------------------------------------------- -/** - * @brief Starts the application - * - * @param argc Number of commandline arguments - * @param argv Commandline arguments - * @return exit code, 0 for normal exit and !=0 for error cases - */ - int main(int argc, char *argv[]) { bool runUnitTests = false; bool simpleBootTest = false; - QString systemIdStr = QString(); + QString systemIdStr; bool hasSystemId = false; bool bypassRunGuard = false; bool stressUnitTests = false; // Stress test unit tests - bool quietWindowsAsserts = false; // Don't let asserts pop dialog boxes + bool quietWindowsAsserts = false; // Suppress Windows assert UI QString unitTestOptions; CmdLineOpt_t rgCmdLineOptions[] = { @@ -70,18 +57,24 @@ int main(int argc, char *argv[]) #endif { "--system-id", &hasSystemId, &systemIdStr }, { "--simple-boot-test", &simpleBootTest, nullptr }, - // Add additional command line option flags here }; ParseCmdLineOptions(argc, argv, rgCmdLineOptions, std::size(rgCmdLineOptions), false); +#ifdef QGC_UNITTEST_BUILD + if (stressUnitTests) { + runUnitTests = true; + } +#ifdef Q_OS_WIN + if (runUnitTests) { + quietWindowsAsserts = true; + } +#endif +#endif + #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - // We make the runguard key different for custom and non custom - // builds, so they can be executed together in the same device. - // Stable and Daily have same QGC_APP_NAME so they would - // not be able to run at the same time + // Single-instance guard for desktop const QString runguardString = QStringLiteral("%1 RunGuardKey").arg(QGC_APP_NAME); - RunGuard guard(runguardString); if (!bypassRunGuard && !guard.tryToRun()) { const QApplication errorApp(argc, argv); @@ -97,46 +90,18 @@ int main(int argc, char *argv[]) const QApplication errorApp(argc, argv); (void) QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("You are running %1 as root. " - "You should not do this since it will cause other issues with %1." - "%1 will now exit.

").arg(QGC_APP_NAME) + "You should not do this since it will cause other issues with %1. " + "%1 will now exit.

").arg(QGC_APP_NAME) ); return -1; } #endif -#ifdef Q_OS_UNIX - if (!qEnvironmentVariableIsSet("QT_ASSUME_STDERR_HAS_CONSOLE")) { - (void) qputenv("QT_ASSUME_STDERR_HAS_CONSOLE", "1"); - } - - if (!qEnvironmentVariableIsSet("QT_FORCE_STDERR_LOGGING")) { - (void) qputenv("QT_FORCE_STDERR_LOGGING", "1"); - } -#endif + // Early platform setup before Qt app construction + Platform::setupPreApp(quietWindowsAsserts); #ifdef Q_OS_WIN - if (!qEnvironmentVariableIsSet("QT_WIN_DEBUG_CONSOLE")) { - (void) qputenv("QT_WIN_DEBUG_CONSOLE", "attach"); // new - } -#endif - - QGCLogging::installHandler(quietWindowsAsserts); - -#ifdef QGC_UNITTEST_BUILD - if (stressUnitTests) { - runUnitTests = true; - } -#endif - -#ifdef Q_OS_MACOS - Platform::disableAppNapViaInfoDict(); -#endif - -#ifdef Q_OS_WIN - // Set our own OpenGL buglist - // (void) qputenv("QT_OPENGL_BUGLIST", ":/opengl/resources/opengl/buglist.json"); - - // Allow for command line override of renderer + // Allow command-line override of renderer for (int i = 0; i < argc; i++) { const QString arg(argv[i]); if (arg == QStringLiteral("-desktop")) { @@ -147,34 +112,26 @@ int main(int argc, char *argv[]) break; } } - -#ifdef QGC_UNITTEST_BUILD - if (runUnitTests) { - // Don't pop up Windows Error Reporting dialog when app crashes. - const DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX); - SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX); - } #endif -#endif // Q_OS_WIN QGCApplication app(argc, argv, runUnitTests, simpleBootTest); -#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) - SignalHandler::instance(); - (void) SignalHandler::setupSignalHandlers(); -#endif + QGCLogging::installHandler(); + + // Late platform setup after app and logging exist + Platform::setupPostApp(); app.init(); - // Set system ID if specified via command line, for example --system-id:255 + // Optional: set MAVLink System ID from CLI, e.g. --system-id:255 if (hasSystemId) { - bool ok; + bool ok = false; const int systemId = systemIdStr.toInt(&ok); - if (ok && (systemId >= 1) && (systemId <= 255)) { // MAVLink system IDs are 8-bit + if (ok && systemId >= 1 && systemId <= 255) { // MAVLink system IDs are 1..255 for GCS use qDebug() << "Setting MAVLink System ID to:" << systemId; SettingsManager::instance()->mavlinkSettings()->gcsMavlinkSystemID()->setRawValue(systemId); } else { - qDebug() << "Not setting MAVLink System ID. It must be between 0 and 255. Invalid system ID value:" << systemIdStr; + qDebug() << "Not setting MAVLink System ID. It must be between 1 and 255. Invalid value:" << systemIdStr; } } @@ -186,18 +143,15 @@ int main(int argc, char *argv[]) } else #endif { - #ifdef Q_OS_ANDROID - AndroidInterface::checkStoragePermissions(); - #endif - +#ifdef Q_OS_ANDROID + AndroidInterface::checkStoragePermissions(); +#endif if (!simpleBootTest) { exitCode = app.exec(); } } app.shutdown(); - qDebug() << "Exiting main"; - return exitCode; }