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