DisplayServer (Linux): wait for delayed Wayland output events

Ref: #2451
This commit is contained in:
Carter Li
2026-07-14 10:56:44 +08:00
parent 8ca6bb084d
commit 29d626f41d
4 changed files with 74 additions and 2 deletions
@@ -25,6 +25,11 @@ static void waylandOutputScaleListener(void* data, [[maybe_unused]] struct wl_ou
display->dpi = 96 * (uint32_t) scale;
}
static void waylandOutputDoneListener(void* data, [[maybe_unused]] struct wl_output* output) {
WaylandDisplay* display = data;
display->done = true;
}
static void waylandOutputGeometryListener(void* data,
[[maybe_unused]] struct wl_output* output,
[[maybe_unused]] int32_t x,
@@ -54,7 +59,7 @@ static void handleXdgLogicalSize(void* data, [[maybe_unused]] struct zxdg_output
static void* outputListener[] = {
waylandOutputGeometryListener, // geometry
waylandOutputModeListener, // mode
stubListener, // done
waylandOutputDoneListener, // done
waylandOutputScaleListener, // scale
ffWaylandOutputNameListener, // name
ffWaylandOutputDescriptionListener, // description
@@ -122,6 +127,13 @@ const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry*
wldata->ffwl_proxy_destroy(output);
return "Failed to roundtrip wl_output";
}
if (bindVersion >= WL_OUTPUT_DONE_SINCE_VERSION && !display.done) {
const char* error = ffWaylandWaitForDone(&display);
if (error) {
wldata->ffwl_proxy_destroy(output);
return error;
}
}
if (wldata->zxdgOutputManager) {
uint32_t bindVersion = min(version, ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION);
@@ -147,11 +147,16 @@ static void waylandKdePriorityListener(void* data, [[maybe_unused]] struct kde_o
display->primary = priority == 1;
}
static void waylandKdeDoneListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2) {
WaylandDisplay* display = data;
display->done = true;
}
static struct kde_output_device_v2_listener outputListener = {
.geometry = waylandKdeGeometryListener,
.current_mode = waylandKdeCurrentModeListener,
.mode = waylandKdeModeListener,
.done = (void*) stubListener,
.done = waylandKdeDoneListener,
.scale = waylandKdeScaleListener,
.edid = waylandKdeEdidListener,
.enabled = waylandKdeEnabledListener,
@@ -211,6 +216,13 @@ static const char* waylandKdeHandleOutput(WaylandData* wldata, struct wl_proxy*
wldata->ffwl_proxy_destroy(output);
return "Failed to roundtrip kde_output_device_v2";
}
if (!display.done) {
const char* error = ffWaylandWaitForDone(&display);
if (error) {
wldata->ffwl_proxy_destroy(output);
return error;
}
}
// Destroy any mode proxies that were created during the listeners.
// wl proxies created for modes are not automatically freed by destroying
// the parent output proxy, so destroy them explicitly to avoid leaks.
@@ -217,6 +217,39 @@ uint32_t ffWaylandHandleRotation(WaylandDisplay* display) {
return rotation;
}
const char* ffWaylandWaitForDone(WaylandDisplay* display) {
int32_t timeout = instance.config.general.processingTimeout;
struct timespec ts;
if (timeout == 0) {
return "timeout is disabled";
} else if (timeout > 0) {
if (!display->parent->ffwl_display_dispatch_timeout) {
return "timeout is enabled, but the libwayland-client version is too old to support it";
}
ts.tv_sec = timeout / 1000;
ts.tv_nsec = (timeout % 1000) * 1000000;
}
WaylandData* wldata = display->parent;
// Some compositors emit the final output state asynchronously after the roundtrip callback (#2074)
while (!display->done) {
if (timeout > 0) {
if (wldata->ffwl_display_dispatch_timeout(wldata->display, &ts) < 0) {
return "wl_display_dispatch_timeout() failed";
}
} else if (timeout < 0) {
if (wldata->ffwl_display_dispatch(wldata->display) < 0) {
return "wl_display_dispatch() failed";
}
} else {
return "Timeout exceeded, but the compositor did not send a done event";
}
}
return nullptr;
}
const char* ffdsConnectWayland(FFDisplayServerResult* result) {
if (getenv("XDG_RUNTIME_DIR") == nullptr) {
return "Wayland requires $XDG_RUNTIME_DIR being set";
@@ -236,6 +269,12 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) {
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_destroy)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_display_roundtrip)
if (instance.config.general.processingTimeout < 0) {
FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(wayland, data, wl_display_dispatch)
} else if (instance.config.general.processingTimeout > 0) {
FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(wayland, data, wl_display_dispatch_timeout)
}
data.display = ffwl_display_connect(nullptr);
if (data.display == nullptr) {
return "wl_display_connect returned nullptr";
@@ -20,12 +20,19 @@ typedef enum WaylandProtocolType: uint8_t {
FF_WAYLAND_PROTOCOL_TYPE_KDE_REGISTRY,
} WaylandProtocolType;
// In case users have an ancient version of libwayland-client
int wl_display_dispatch_timeout(struct wl_display *display, const struct timespec *timeout);
typedef struct WaylandData {
FFDisplayServerResult* result;
FF_LIBRARY_SYMBOL(wl_proxy_marshal_constructor_versioned)
FF_LIBRARY_SYMBOL(wl_proxy_add_listener)
FF_LIBRARY_SYMBOL(wl_proxy_destroy)
FF_LIBRARY_SYMBOL(wl_display_roundtrip)
union {
FF_LIBRARY_SYMBOL(wl_display_dispatch)
FF_LIBRARY_SYMBOL(wl_display_dispatch_timeout)
};
struct wl_display* display;
WaylandProtocolType protocolType;
struct wl_proxy* zxdgOutputManager;
@@ -58,6 +65,7 @@ typedef struct WaylandDisplay {
FFstrbuf serial;
uint8_t bitDepth;
bool primary;
bool done;
} WaylandDisplay;
inline static void stubListener(void* data, ...) {
@@ -79,6 +87,7 @@ void ffWaylandOutputNameListener(void* data, [[maybe_unused]] void* output, cons
void ffWaylandOutputDescriptionListener(void* data, [[maybe_unused]] void* output, const char* description);
// Modifies content of display. Don't call this function when calling ffdsAppendDisplay
uint32_t ffWaylandHandleRotation(WaylandDisplay* display);
const char* ffWaylandWaitForDone(WaylandDisplay* display);
const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);
const char* ffWaylandHandleKdeOutputRegistry(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);