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#ifdef Q_OS_WIN
216 // Qt 6 no longer ships opengl32sw.dll, so stay on D3D and pick the built-in WARP adapter instead.
217 (void) qputenv("QSG_RHI_PREFER_SOFTWARE_RENDERER", "1");
218#else
219 // RHI defaults to Metal on macOS; AA_UseSoftwareOpenGL only bites once the scene graph is on GL.
220 QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
221 QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
222#endif
223 }
224#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) && \
225 (defined(QGC_HAS_GST_GLMEMORY_GPU_PATH) || defined(QGC_HAS_GST_DMABUF_GPU_PATH))
226 // GL is the only working desktop-Linux GStreamer zero-copy backend (GLMemory and DMABuf/EGLImage both import into a
227 // GL RHI; Vulkan import dormant); pin it unless the user set QSG_RHI_BACKEND. No QRhi::probe — needs GuiPrivate (not
228 // linked here) and GL is always present on Linux.
229 else if (!qEnvironmentVariableIsSet("QSG_RHI_BACKEND")) {
230 QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
231 }
232#endif
233
234 // GStreamer's GL/DMABuf zero-copy paths both need QOpenGLContext::globalShareContext(), which this attribute enables.
235#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH) || defined(QGC_HAS_GST_DMABUF_GPU_PATH)
236 QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
237#endif
238 QCoreApplication::setAttribute(Qt::AA_CompressTabletEvents);
239
240 return std::nullopt;
241}
242
244{
245#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID)
246 SignalHandler* signalHandler = new SignalHandler(QCoreApplication::instance());
247 (void) signalHandler->setupSignalHandlers();
248#endif
249}
250
251#if (defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)) && !defined(Q_OS_ANDROID)
252bool Platform::isRunningAsRoot()
253{
254 return ::getuid() == 0;
255}
256
257int Platform::showRootError([[maybe_unused]] int argc, [[maybe_unused]] char *argv[])
258{
259 const QString message = QCoreApplication::translate("main",
260 "You are running %1 as root. "
261 "You should not do this since it will cause other issues with %1. "
262 "%1 will now exit.").arg(QLatin1String(QGC_APP_NAME));
263 showLinuxErrorDialog(message.toLocal8Bit());
264 return -1;
265}
266#endif
267
268#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
269int Platform::showMultipleInstanceError([[maybe_unused]] int argc, [[maybe_unused]] char *argv[])
270{
271 const QString message = QCoreApplication::translate("main",
272 "A second instance of %1 is already running. "
273 "Please close the other instance and try again.").arg(QLatin1String(QGC_APP_NAME));
274#if defined(Q_OS_MACOS)
275 // The native alert is GUI-only; also write to stderr so a CLI/headless launch sees the reason.
276 fprintf(stderr, "Error: %s\n", message.toLocal8Bit().constData());
277 CFStringRef cfMessage = CFStringCreateWithCString(nullptr, message.toUtf8().constData(), kCFStringEncodingUTF8);
278 CFUserNotificationDisplayAlert(0, kCFUserNotificationStopAlertLevel,
279 nullptr, nullptr, nullptr,
280 CFSTR("Error"), cfMessage,
281 nullptr, nullptr, nullptr, nullptr);
282 CFRelease(cfMessage);
283#elif defined(Q_OS_WIN)
284 // MessageBoxW is GUI-only; also write to stderr so a CLI/headless launch sees the reason.
285 fprintf(stderr, "Error: %s\n", message.toLocal8Bit().constData());
286 MessageBoxW(nullptr, message.toStdWString().c_str(), L"Error", MB_OK | MB_ICONERROR);
287#else
288 showLinuxErrorDialog(message.toLocal8Bit());
289#endif
290 return -1;
291}
292
293bool Platform::checkSingleInstance(bool allowMultiple)
294{
295 if (allowMultiple) {
296 return true;
297 }
298
299 static const QString runguardString = QStringLiteral("%1 RunGuardKey").arg(QLatin1String(QGC_APP_NAME));
300 static RunGuard guard(runguardString);
301 return guard.tryToRun();
302}
303#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:293
void setupPostApp()
Complete platform setup after application exists.
Definition Platform.cc:243
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:269
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 rendering.
bool onscreen
Show test windows on screen (skip offscreen override)