wayland: Fix a race condition in the color management event handlers

The queue running on the cursor thread might flush color events before their queue has been set. Use proxy wrappers for their parent objects to assign the queue atomically at creation time.
This commit is contained in:
Frank Praznik
2025-10-18 14:16:45 -04:00
parent 35cc58e027
commit c84ac6d6bc
5 changed files with 59 additions and 14 deletions

View File

@@ -33,6 +33,7 @@ typedef struct Wayland_ColorInfoState
{ {
struct wp_image_description_v1 *wp_image_description; struct wp_image_description_v1 *wp_image_description;
struct wp_image_description_info_v1 *wp_image_description_info; struct wp_image_description_info_v1 *wp_image_description_info;
struct wl_event_queue *queue;
union union
{ {
@@ -73,6 +74,9 @@ void Wayland_FreeColorInfoState(Wayland_ColorInfoState *state)
{ {
if (state) { if (state) {
Wayland_CancelColorInfoRequest(state); Wayland_CancelColorInfoRequest(state);
if (state->queue) {
WAYLAND_wl_event_queue_destroy(state->queue);
}
switch (state->object_type) { switch (state->object_type) {
case WAYLAND_COLOR_OBJECT_TYPE_WINDOW: case WAYLAND_COLOR_OBJECT_TYPE_WINDOW:
@@ -212,18 +216,10 @@ static void PumpColorspaceEvents(Wayland_ColorInfoState *state)
SDL_VideoData *vid = SDL_GetVideoDevice()->internal; SDL_VideoData *vid = SDL_GetVideoDevice()->internal;
// Run the image description sequence to completion in its own queue. // Run the image description sequence to completion in its own queue.
struct wl_event_queue *queue = WAYLAND_wl_display_create_queue(vid->display);
if (state->deferred_event_processing) {
WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description_info, queue);
} else {
WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description, queue);
}
while (state->wp_image_description) { while (state->wp_image_description) {
WAYLAND_wl_display_dispatch_queue(vid->display, queue); WAYLAND_wl_display_dispatch_queue(vid->display, state->queue);
} }
WAYLAND_wl_event_queue_destroy(queue);
Wayland_FreeColorInfoState(state); Wayland_FreeColorInfoState(state);
} }
@@ -246,8 +242,20 @@ static void image_description_handle_ready(void *data,
{ {
Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data; Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
// This will inherit the queue of the factory image description object. /* If event processing was deferred, then the image description is on the default queue.
* Otherwise, it will inherit the queue from the image description object.
*/
if (state->deferred_event_processing) {
SDL_VideoData *vid = SDL_GetVideoDevice()->internal;
state->queue = Wayland_DisplayCreateQueue(vid->display, "SDL Color Management Queue");
struct wl_proxy *image_desc_wrapper = WAYLAND_wl_proxy_create_wrapper(state->wp_image_description);
WAYLAND_wl_proxy_set_queue(image_desc_wrapper, state->queue);
state->wp_image_description_info = wp_image_description_v1_get_information((struct wp_image_description_v1 *)image_desc_wrapper);
WAYLAND_wl_proxy_wrapper_destroy(image_desc_wrapper);
} else {
state->wp_image_description_info = wp_image_description_v1_get_information(state->wp_image_description); state->wp_image_description_info = wp_image_description_v1_get_information(state->wp_image_description);
}
wp_image_description_info_v1_add_listener(state->wp_image_description_info, &image_description_info_listener, data); wp_image_description_info_v1_add_listener(state->wp_image_description_info, &image_description_info_listener, data);
if (state->deferred_event_processing) { if (state->deferred_event_processing) {
@@ -271,7 +279,17 @@ void Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, bool defer_event
state->window_data = window_data; state->window_data = window_data;
state->object_type = WAYLAND_COLOR_OBJECT_TYPE_WINDOW; state->object_type = WAYLAND_COLOR_OBJECT_TYPE_WINDOW;
state->deferred_event_processing = defer_event_processing; state->deferred_event_processing = defer_event_processing;
if (!defer_event_processing) {
state->queue = Wayland_DisplayCreateQueue(window_data->waylandData->display, "SDL Color Management Queue");
struct wl_proxy *surface_feedback_wrapper = WAYLAND_wl_proxy_create_wrapper(window_data->wp_color_management_surface_feedback);
WAYLAND_wl_proxy_set_queue(surface_feedback_wrapper, state->queue);
state->wp_image_description = wp_color_management_surface_feedback_v1_get_preferred((struct wp_color_management_surface_feedback_v1 *)surface_feedback_wrapper);
WAYLAND_wl_proxy_wrapper_destroy(surface_feedback_wrapper);
} else {
state->wp_image_description = wp_color_management_surface_feedback_v1_get_preferred(window_data->wp_color_management_surface_feedback); state->wp_image_description = wp_color_management_surface_feedback_v1_get_preferred(window_data->wp_color_management_surface_feedback);
}
wp_image_description_v1_add_listener(state->wp_image_description, &image_description_listener, state); wp_image_description_v1_add_listener(state->wp_image_description, &image_description_listener, state);
if (!defer_event_processing) { if (!defer_event_processing) {
@@ -291,7 +309,17 @@ void Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, bool defer_eve
state->display_data = display_data; state->display_data = display_data;
state->object_type = WAYLAND_COLOR_OBJECT_TYPE_DISPLAY; state->object_type = WAYLAND_COLOR_OBJECT_TYPE_DISPLAY;
state->deferred_event_processing = defer_event_processing; state->deferred_event_processing = defer_event_processing;
if (!defer_event_processing) {
state->queue = Wayland_DisplayCreateQueue(display_data->videodata->display, "SDL Color Management Queue");
struct wl_proxy *output_feedback_wrapper = WAYLAND_wl_proxy_create_wrapper(display_data->wp_color_management_output);
WAYLAND_wl_proxy_set_queue(output_feedback_wrapper, state->queue);
state->wp_image_description = wp_color_management_output_v1_get_image_description((struct wp_color_management_output_v1 *)output_feedback_wrapper);
WAYLAND_wl_proxy_wrapper_destroy(output_feedback_wrapper);
} else {
state->wp_image_description = wp_color_management_output_v1_get_image_description(display_data->wp_color_management_output); state->wp_image_description = wp_color_management_output_v1_get_image_description(display_data->wp_color_management_output);
}
wp_image_description_v1_add_listener(state->wp_image_description, &image_description_listener, state); wp_image_description_v1_add_listener(state->wp_image_description, &image_description_listener, state);
if (!defer_event_processing) { if (!defer_event_processing) {

View File

@@ -390,7 +390,7 @@ static int SDLCALL Wayland_CursorThreadFunc(void *data)
static bool Wayland_StartCursorThread(SDL_VideoData *data) static bool Wayland_StartCursorThread(SDL_VideoData *data)
{ {
if (!cursor_thread_context.thread) { if (!cursor_thread_context.thread) {
cursor_thread_context.queue = WAYLAND_wl_display_create_queue(data->display); cursor_thread_context.queue = Wayland_DisplayCreateQueue(data->display, "SDL Cursor Surface Queue");
if (!cursor_thread_context.queue) { if (!cursor_thread_context.queue) {
goto cleanup; goto cleanup;
} }

View File

@@ -88,6 +88,10 @@ SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_flags, (struct wl_proxy *pro
SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_array_flags, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interface, uint32_t version, uint32_t flags, union wl_argument *args)) SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_array_flags, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interface, uint32_t version, uint32_t flags, union wl_argument *args))
#endif #endif
#if SDL_WAYLAND_CHECK_VERSION(1, 23, 0) || defined(SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC)
SDL_WAYLAND_SYM_OPT(struct wl_event_queue *, wl_display_create_queue_with_name, (struct wl_display *display, const char *name))
#endif
#if 0 // TODO RECONNECT: See waylandvideo.c for more information! #if 0 // TODO RECONNECT: See waylandvideo.c for more information!
#if SDL_WAYLAND_CHECK_VERSION(broken, on, purpose) #if SDL_WAYLAND_CHECK_VERSION(broken, on, purpose)
SDL_WAYLAND_SYM(int, wl_display_reconnect, (struct wl_display *)) SDL_WAYLAND_SYM(int, wl_display_reconnect, (struct wl_display *))

View File

@@ -457,6 +457,18 @@ SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface)
return NULL; return NULL;
} }
struct wl_event_queue *Wayland_DisplayCreateQueue(struct wl_display *display, const char *name)
{
#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC
if (WAYLAND_wl_display_create_queue_with_name) {
return WAYLAND_wl_display_create_queue_with_name(display, name);
}
#elif SDL_WAYLAND_CHECK_VERSION(1, 23, 0)
return WAYLAND_wl_display_create_queue_with_name(display, name);
#endif
return WAYLAND_wl_display_create_queue(display);
}
static void Wayland_DeleteDevice(SDL_VideoDevice *device) static void Wayland_DeleteDevice(SDL_VideoDevice *device)
{ {
SDL_VideoData *data = device->internal; SDL_VideoData *data = device->internal;

View File

@@ -137,6 +137,7 @@ extern bool SDL_WAYLAND_own_output(struct wl_output *output);
extern SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface); extern SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface);
void Wayland_AddWindowDataToExternalList(SDL_WindowData *data); void Wayland_AddWindowDataToExternalList(SDL_WindowData *data);
void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data); void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data);
struct wl_event_queue *Wayland_DisplayCreateQueue(struct wl_display *display, const char *name);
extern bool Wayland_LoadLibdecor(SDL_VideoData *data, bool ignore_xdg); extern bool Wayland_LoadLibdecor(SDL_VideoData *data, bool ignore_xdg);