QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
Platform.cc
Go to the documentation of this file.
1#include "Platform.h"
2#include "qgc_version.h"
3
4#include <QtCore/QCoreApplication>
5#include <QtCore/QProcessEnvironment>
6
8
9#ifdef Q_OS_ANDROID
10 #include "AndroidInterface.h"
11#endif
12
13#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID)
14 #include "RunGuard.h"
15 #include "SignalHandler.h"
16#endif
17
18#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
19 #include <cstdio>
20 #include <unistd.h>
21 #include <sys/types.h>
22 #include <sys/wait.h>
23#endif
24
25#ifdef QGC_HAS_LIBGCRYPT
26 #include <gcrypt.h>
27#endif
28
29#if defined(Q_OS_MACOS)
30 #include <CoreFoundation/CoreFoundation.h>
31#elif defined(Q_OS_WIN)
32 #include <qt_windows.h>
33 #include <iostream>
34 #include <iterator> // std::size
35 #include <cwchar> // swprintf
36 #if defined(_MSC_VER)
37 #include <crtdbg.h>
38 #include <stdlib.h>
39 #include <cstdio> // _snwprintf_s
40 #endif
41#endif
42
43namespace {
44
45#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
46static void showLinuxErrorDialog(const QByteArray& msg)
47{
48 // Try to show a GUI dialog — important for AppImage users where stderr is invisible.
49 // Fork a child and attempt dialog tools in order of preference; no shell is invoked.
50 const pid_t pid = fork();
51 if (pid == 0) {
52 const QByteArray zenityText = QByteArrayLiteral("--text=") + msg;
53 execlp("zenity", "zenity", "--error", "--title=Error", zenityText.constData(), nullptr);
54 execlp("kdialog", "kdialog", "--error", msg.constData(), nullptr);
55 execlp("xmessage", "xmessage", "-center", msg.constData(), nullptr);
56 _exit(1);
57 } else if (pid > 0) {
58 int status = 0;
59 (void) waitpid(pid, &status, 0);
60 }
61 // Always write to stderr as well
62 fprintf(stderr, "Error: %s\n", msg.constData());
63}
64#endif // Q_OS_LINUX
65
66#if defined(Q_OS_MACOS)
67void disableAppNapViaInfoDict()
68{
69 CFBundleRef bundle = CFBundleGetMainBundle();
70 if (!bundle) {
71 return;
72 }
73 CFMutableDictionaryRef infoDict = const_cast<CFMutableDictionaryRef>(CFBundleGetInfoDictionary(bundle));
74 if (infoDict) {
75 CFDictionarySetValue(infoDict, CFSTR("NSAppSleepDisabled"), kCFBooleanTrue);
76 }
77}
78#endif // Q_OS_MACOS
79
80#if defined(Q_OS_WIN)
81
82#if defined(_MSC_VER)
83int __cdecl WindowsCrtReportHook(int reportType, char* message, int* returnValue)
84{
85 if (message) {
86 std::cerr << message << std::endl;
87 }
88 if (reportType == _CRT_ASSERT) {
89 if (returnValue) {
90 *returnValue = 0;
91 }
92 return 1; // handled
93 }
94 return 0; // let CRT continue
95}
96
97void __cdecl WindowsPurecallHandler()
98{
99 (void) OutputDebugStringW(L"QGC: _purecall\n");
100}
101
102void WindowsInvalidParameterHandler([[maybe_unused]] const wchar_t* expression,
103 [[maybe_unused]] const wchar_t* function,
104 [[maybe_unused]] const wchar_t* file,
105 [[maybe_unused]] unsigned int line,
106 [[maybe_unused]] uintptr_t pReserved)
107{
108
109}
110#endif // _MSC_VER
111
112LPTOP_LEVEL_EXCEPTION_FILTER g_prevUef = nullptr;
113
114LONG WINAPI WindowsUnhandledExceptionFilter(EXCEPTION_POINTERS* ep)
115{
116 const DWORD code = (ep && ep->ExceptionRecord) ? ep->ExceptionRecord->ExceptionCode : 0;
117 wchar_t buf[128] = {};
118#if defined(_MSC_VER)
119 (void) _snwprintf_s(buf, _TRUNCATE, L"QGC: unhandled SEH 0x%08lX\n", static_cast<unsigned long>(code));
120#else
121 (void) swprintf(buf, static_cast<int>(std::size(buf)), L"QGC: unhandled SEH 0x%08lX\n", static_cast<unsigned long>(code));
122#endif
123 (void) OutputDebugStringW(buf);
124
125 const HANDLE h = GetStdHandle(STD_ERROR_HANDLE);
126 if (h && (h != INVALID_HANDLE_VALUE)) {
127 DWORD ignored = 0;
128 const char narrow[] = "QGC: unhandled SEH\n";
129 (void) WriteFile(h, narrow, (DWORD)sizeof(narrow) - 1, &ignored, nullptr);
130 }
131
132 return EXCEPTION_EXECUTE_HANDLER;
133}
134
135void setWindowsErrorModes(bool quietWindowsAsserts)
136{
137 (void) SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
138 g_prevUef = SetUnhandledExceptionFilter(WindowsUnhandledExceptionFilter);
139
140#if defined(_MSC_VER)
141 (void) _set_invalid_parameter_handler(WindowsInvalidParameterHandler);
142 (void) _set_purecall_handler(WindowsPurecallHandler);
143
144 if (quietWindowsAsserts) {
145 (void) _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
146 (void) _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
147 (void) _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
148 (void) _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, WindowsCrtReportHook);
149 (void) _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
150 (void) _set_error_mode(_OUT_TO_STDERR);
151 }
152#else
153 Q_UNUSED(quietWindowsAsserts);
154#endif
155}
156#endif // Q_OS_WIN
157
158} // namespace
159
160std::optional<int> Platform::initialize(int argc, char* argv[],
162{
163#ifdef QGC_HAS_LIBGCRYPT
164 // qtkeychain's libsecret backend uses libgcrypt; init must run before Qt threads start.
165 // nullptr (not GCRYPT_VERSION) — build-host/runtime version skew shouldn't silently skip init.
166 if (gcry_check_version(nullptr)) {
167 (void) gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
168 (void) gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
169 }
170#endif
171
172#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
173 if (isRunningAsRoot()) {
174 return showRootError(argc, argv);
175 }
176#endif
177
178#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
179 const bool allowMultiple = args.allowMultiple || args.runningUnitTests || args.listTests;
180 if (!checkSingleInstance(allowMultiple)) {
181 return showMultipleInstanceError(argc, argv);
182 }
183#else
184 Q_UNUSED(argc);
185 Q_UNUSED(argv);
186#endif
187
188#ifdef Q_OS_UNIX
189#ifndef Q_OS_ANDROID
190 // On Android, skip these — either env var triggers shouldLogToStderr(),
191 // which bypasses Qt's __android_log_print path to logcat.
192 if (!qEnvironmentVariableIsSet("QT_ASSUME_STDERR_HAS_CONSOLE")) {
193 (void) qputenv("QT_ASSUME_STDERR_HAS_CONSOLE", "1");
194 }
195 if (!qEnvironmentVariableIsSet("QT_FORCE_STDERR_LOGGING")) {
196 (void) qputenv("QT_FORCE_STDERR_LOGGING", "1");
197 }
198#endif
199#endif
200
201#ifdef Q_OS_WIN
202 if (!qEnvironmentVariableIsSet("QT_WIN_DEBUG_CONSOLE")) {
203 (void) qputenv("QT_WIN_DEBUG_CONSOLE", "attach");
204 }
205 setWindowsErrorModes(args.quietWindowsAsserts);
206#endif
207
208#ifdef Q_OS_MACOS
209 disableAppNapViaInfoDict();
210#endif
211
212#ifdef QGC_UNITTEST_BUILD
213 if (args.runningUnitTests || args.listTests) {
214 if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) {
215 (void) qputenv("QT_QPA_PLATFORM", "offscreen");
216 }
217 }
218#endif
219
220 // --- Qt attributes ---
221 if (args.useDesktopGL) {
222 QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
223 }
224
225 if (args.useSwRast) {
226 QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
227 }
228
229 QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
230 QCoreApplication::setAttribute(Qt::AA_CompressTabletEvents);
231
232 return std::nullopt;
233}
234
236{
237#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID)
238 SignalHandler* signalHandler = new SignalHandler(QCoreApplication::instance());
239 (void) signalHandler->setupSignalHandlers();
240#endif
241
242#ifdef Q_OS_ANDROID
244#endif
245}
246
247#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
248bool Platform::isRunningAsRoot()
249{
250 return ::getuid() == 0;
251}
252
253int Platform::showRootError([[maybe_unused]] int argc, [[maybe_unused]] char *argv[])
254{
255 const QString message = QCoreApplication::translate("main",
256 "You are running %1 as root. "
257 "You should not do this since it will cause other issues with %1. "
258 "%1 will now exit.").arg(QLatin1String(QGC_APP_NAME));
259 showLinuxErrorDialog(message.toLocal8Bit());
260 return -1;
261}
262#endif
263
264#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
265int Platform::showMultipleInstanceError([[maybe_unused]] int argc, [[maybe_unused]] char *argv[])
266{
267 const QString message = QCoreApplication::translate("main",
268 "A second instance of %1 is already running. "
269 "Please close the other instance and try again.").arg(QLatin1String(QGC_APP_NAME));
270#if defined(Q_OS_MACOS)
271 CFStringRef cfMessage = CFStringCreateWithCString(nullptr, message.toUtf8().constData(), kCFStringEncodingUTF8);
272 CFUserNotificationDisplayAlert(0, kCFUserNotificationStopAlertLevel,
273 nullptr, nullptr, nullptr,
274 CFSTR("Error"), cfMessage,
275 nullptr, nullptr, nullptr, nullptr);
276 CFRelease(cfMessage);
277#elif defined(Q_OS_WIN)
278 MessageBoxW(nullptr, message.toStdWString().c_str(), L"Error", MB_OK | MB_ICONERROR);
279#else
280 showLinuxErrorDialog(message.toLocal8Bit());
281#endif
282 return -1;
283}
284
285bool Platform::checkSingleInstance(bool allowMultiple)
286{
287 if (allowMultiple) {
288 return true;
289 }
290
291 static const QString runguardString = QStringLiteral("%1 RunGuardKey").arg(QLatin1String(QGC_APP_NAME));
292 static RunGuard guard(runguardString);
293 return guard.tryToRun();
294}
295#endif
bool tryToRun()
Definition RunGuard.cc:50
int setupSignalHandlers()
bool checkSingleInstance(bool allowMultiple)
Check if another instance is already running (single instance guard)
Definition Platform.cc:285
void setupPostApp()
Complete platform setup after application exists.
Definition Platform.cc:235
std::optional< int > initialize(int argc, char *argv[], const QGCCommandLineParser::CommandLineParseResult &args)
Initialize platform: run safety checks and configure environment.
Definition Platform.cc:160
int showMultipleInstanceError(int argc, char *argv[])
Show error dialog when another instance is already running.
Definition Platform.cc:265
Result of parsing command-line arguments.
bool quietWindowsAsserts
Windows only: Disable assert dialogs.
bool listTests
List available tests and exit.
bool useDesktopGL
Windows only: Force Desktop OpenGL.
bool useSwRast
Windows/macOS: Force software OpenGL.