diff --git a/src/detection/displayserver/linux/wayland/global-output.c b/src/detection/displayserver/linux/wayland/global-output.c index e3c8018536..573f23f903 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 d26cfbc688..0464ab4370 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 7e37aee8dd..92f10a9899 100644 --- a/src/detection/displayserver/linux/wayland/wayland.c +++ b/src/detection/displayserver/linux/wayland/wayland.c @@ -11,6 +11,7 @@ #include #include "common/properties.h" + #include "common/time.h" #include "wayland.h" #include "kde-output-device-v2-client-protocol.h" @@ -217,6 +218,26 @@ uint32_t ffWaylandHandleRotation(WaylandDisplay* display) { return rotation; } +const char* ffWaylandWaitForDone(WaylandDisplay* display) { + WaylandData* wldata = display->parent; + double deadline = ffTimeGetTick() + instance.config.general.processingTimeout; + + // Some compositors emit the final output state asynchronously after the roundtrip callback (#2074) + while (!display->done) { + if (ffTimeGetTick() >= deadline) { + return "Timeout waiting for Wayland output information"; + } + if (wldata->ffwl_display_roundtrip(wldata->display) < 0) { + return "Failed to roundtrip Wayland output information"; + } + if (!display->done) { + ffTimeSleep(1); + } + } + + return nullptr; +} + const char* ffdsConnectWayland(FFDisplayServerResult* result) { if (getenv("XDG_RUNTIME_DIR") == nullptr) { return "Wayland requires $XDG_RUNTIME_DIR being set"; diff --git a/src/detection/displayserver/linux/wayland/wayland.h b/src/detection/displayserver/linux/wayland/wayland.h index a661936cf0..e682b3ac51 100644 --- a/src/detection/displayserver/linux/wayland/wayland.h +++ b/src/detection/displayserver/linux/wayland/wayland.h @@ -58,6 +58,7 @@ typedef struct WaylandDisplay { FFstrbuf serial; uint8_t bitDepth; bool primary; + bool done; } WaylandDisplay; inline static void stubListener(void* data, ...) { @@ -79,6 +80,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);