diff --git a/src/detection/displayserver/linux/wayland/global-output.c b/src/detection/displayserver/linux/wayland/global-output.c index e3c801853..573f23f90 100644 --- a/src/detection/displayserver/linux/wayland/global-output.c +++ b/src/detection/displayserver/linux/wayland/global-output.c @@ -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); diff --git a/src/detection/displayserver/linux/wayland/kde-output.c b/src/detection/displayserver/linux/wayland/kde-output.c index d26cfbc68..0464ab437 100644 --- a/src/detection/displayserver/linux/wayland/kde-output.c +++ b/src/detection/displayserver/linux/wayland/kde-output.c @@ -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. diff --git a/src/detection/displayserver/linux/wayland/wayland.c b/src/detection/displayserver/linux/wayland/wayland.c index 7e37aee8d..6b9c41b69 100644 --- a/src/detection/displayserver/linux/wayland/wayland.c +++ b/src/detection/displayserver/linux/wayland/wayland.c @@ -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"; diff --git a/src/detection/displayserver/linux/wayland/wayland.h b/src/detection/displayserver/linux/wayland/wayland.h index a661936cf..257498c0f 100644 --- a/src/detection/displayserver/linux/wayland/wayland.h +++ b/src/detection/displayserver/linux/wayland/wayland.h @@ -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);