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