Skip to content

Commit f7fa64a

Browse files
Force minimal QPA when no display endpoint
Add detection for reachable Wayland/X11 endpoints and fall back to the minimal QPA if none are present. Introduce QDir include and helper functions has_wayland_display_endpoint(), has_x11_display_endpoint(), and should_force_headless_qpa_fallback() to check WAYLAND_DISPLAY and DISPLAY (including XDG_RUNTIME_DIR sockets and /tmp/.X11-unix/X<n> sockets, as well as remote/TCP displays). If QT_QPA_PLATFORM is unset and no local display endpoints are found, set QT_QPA_PLATFORM=minimal before creating QApplication and emit a log via g_log_cb. This prevents trying to use GUI QPAs when no local display is reachable.
1 parent 9a9865f commit f7fa64a

1 file changed

Lines changed: 76 additions & 0 deletions

File tree

src/tray_linux.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <QDBusPendingCall>
2323
#include <QDBusPendingCallWatcher>
2424
#include <QDBusPendingReply>
25+
#include <QDir>
2526
#include <QFileInfo>
2627
#include <QGuiApplication>
2728
#include <QIcon>
@@ -116,6 +117,75 @@ namespace {
116117
return !qgetenv("WAYLAND_DISPLAY").isEmpty();
117118
}
118119

120+
bool has_wayland_display_endpoint() {
121+
const QByteArray wayland_display = qgetenv("WAYLAND_DISPLAY");
122+
if (wayland_display.isEmpty()) {
123+
return false;
124+
}
125+
126+
const QString display_name = QString::fromLocal8Bit(wayland_display).trimmed();
127+
if (display_name.isEmpty()) {
128+
return false;
129+
}
130+
131+
if (const QFileInfo direct_path(display_name); direct_path.exists()) {
132+
return true;
133+
}
134+
135+
const QByteArray runtime_dir = qgetenv("XDG_RUNTIME_DIR");
136+
if (runtime_dir.isEmpty()) {
137+
return false;
138+
}
139+
140+
const QString socket_path = QDir(QString::fromLocal8Bit(runtime_dir)).filePath(display_name);
141+
return QFileInfo::exists(socket_path);
142+
}
143+
144+
bool has_x11_display_endpoint() {
145+
const QByteArray display_env = qgetenv("DISPLAY");
146+
if (display_env.isEmpty()) {
147+
return false;
148+
}
149+
150+
const QString display = QString::fromLocal8Bit(display_env).trimmed();
151+
if (display.isEmpty()) {
152+
return false;
153+
}
154+
155+
if (display.startsWith('/')) {
156+
return QFileInfo::exists(display);
157+
}
158+
159+
if (!display.startsWith(':')) {
160+
// Remote/TCP displays are not locally discoverable; treat as potentially usable.
161+
return true;
162+
}
163+
164+
int digit_end = 1;
165+
while (digit_end < display.size() && display.at(digit_end).isDigit()) {
166+
digit_end++;
167+
}
168+
if (digit_end == 1) {
169+
return true;
170+
}
171+
172+
bool ok = false;
173+
const int display_number = display.mid(1, digit_end - 1).toInt(&ok);
174+
if (!ok) {
175+
return true;
176+
}
177+
178+
const QString socket_path = QStringLiteral("/tmp/.X11-unix/X%1").arg(display_number);
179+
return QFileInfo::exists(socket_path);
180+
}
181+
182+
bool should_force_headless_qpa_fallback() {
183+
if (!qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM")) {
184+
return false;
185+
}
186+
return !has_wayland_display_endpoint() && !has_x11_display_endpoint();
187+
}
188+
119189
QPoint screen_anchor_point(const QScreen *screen) {
120190
if (screen == nullptr) {
121191
return QPoint();
@@ -729,6 +799,12 @@ extern "C" {
729799

730800
int tray_init(struct tray *tray) {
731801
if (QApplication::instance() == nullptr) {
802+
if (should_force_headless_qpa_fallback()) {
803+
qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("minimal"));
804+
if (g_log_cb != nullptr) {
805+
g_log_cb(2, "Qt tray: no reachable WAYLAND_DISPLAY or DISPLAY endpoint, forcing QT_QPA_PLATFORM=minimal");
806+
}
807+
}
732808
static int argc = 0;
733809
g_app = std::make_unique<QApplication>(argc, nullptr);
734810
g_app_owned = true;

0 commit comments

Comments
 (0)