From fadb261b66ee5817008a813a9ea16feb9ec73b31 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Sat, 12 Oct 2024 12:10:32 -0400
Subject: [PATCH] wayland: Add color manager protocol support
Support the official wp_color_manager_v1 protocol.
---
src/video/wayland/SDL_waylandcolor.c | 224 +++
src/video/wayland/SDL_waylandcolor.h | 41 +
src/video/wayland/SDL_waylandvideo.c | 69 +-
src/video/wayland/SDL_waylandvideo.h | 4 +
src/video/wayland/SDL_waylandwindow.c | 55 +-
src/video/wayland/SDL_waylandwindow.h | 4 +
wayland-protocols/color-management-v1.xml | 1631 +++++++++++++++++++++
7 files changed, 2022 insertions(+), 6 deletions(-)
create mode 100644 src/video/wayland/SDL_waylandcolor.c
create mode 100644 src/video/wayland/SDL_waylandcolor.h
create mode 100644 wayland-protocols/color-management-v1.xml
diff --git a/src/video/wayland/SDL_waylandcolor.c b/src/video/wayland/SDL_waylandcolor.c
new file mode 100644
index 0000000000000..f2bb4552afc2f
--- /dev/null
+++ b/src/video/wayland/SDL_waylandcolor.c
@@ -0,0 +1,224 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "SDL_internal.h"
+
+#ifdef SDL_VIDEO_DRIVER_WAYLAND
+
+#include "SDL_waylandcolor.h"
+#include "SDL_waylandvideo.h"
+#include "SDL_waylandwindow.h"
+#include "color-management-v1-client-protocol.h"
+
+typedef struct Wayland_ColorInfoState
+{
+ struct wp_image_description_v1 *wp_image_description;
+ struct wp_image_description_info_v1 *wp_image_description_info;
+ Wayland_ColorInfo *info;
+
+ bool result;
+} Wayland_ColorInfoState;
+
+static void image_description_info_handle_done(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1)
+{
+ Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
+
+ if (state->wp_image_description_info) {
+ wp_image_description_info_v1_destroy(state->wp_image_description_info);
+ state->wp_image_description_info = NULL;
+ }
+ if (state->wp_image_description) {
+ wp_image_description_v1_destroy(state->wp_image_description);
+ state->wp_image_description = NULL;
+ }
+
+ state->result = true;
+}
+
+static void image_description_info_handle_icc_file(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1,
+ int32_t icc, uint32_t icc_size)
+{
+ Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
+
+ state->info->icc_fd = icc;
+ state->info->icc_size = icc_size;
+}
+
+static void image_description_info_handle_primaries(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1,
+ int32_t r_x, int32_t r_y,
+ int32_t g_x, int32_t g_y,
+ int32_t b_x, int32_t b_y,
+ int32_t w_x, int32_t w_y)
+{
+ // NOP
+}
+
+static void image_description_info_handle_primaries_named(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1,
+ uint32_t primaries)
+{
+ // NOP
+}
+
+static void image_description_info_handle_tf_power(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1,
+ uint32_t eexp)
+{
+ // NOP
+}
+
+static void image_description_info_handle_tf_named(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1,
+ uint32_t tf)
+{
+ // NOP
+}
+
+static void image_description_info_handle_luminances(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1,
+ uint32_t min_lum,
+ uint32_t max_lum,
+ uint32_t reference_lum)
+{
+ Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
+ state->info->HDR.HDR_headroom = (float)max_lum / (float)reference_lum;
+}
+
+static void image_description_info_handle_target_primaries(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1,
+ int32_t r_x, int32_t r_y,
+ int32_t g_x, int32_t g_y,
+ int32_t b_x, int32_t b_y,
+ int32_t w_x, int32_t w_y)
+{
+ // NOP
+}
+
+static void image_description_info_handle_target_luminance(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1,
+ uint32_t min_lum,
+ uint32_t max_lum)
+{
+ // NOP
+}
+
+static void image_description_info_handle_target_max_cll(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1,
+ uint32_t max_cll)
+{
+ // NOP
+}
+
+static void image_description_info_handle_target_max_fall(void *data,
+ struct wp_image_description_info_v1 *wp_image_description_info_v1,
+ uint32_t max_fall)
+{
+ // NOP
+}
+
+static const struct wp_image_description_info_v1_listener image_description_info_listener = {
+ image_description_info_handle_done,
+ image_description_info_handle_icc_file,
+ image_description_info_handle_primaries,
+ image_description_info_handle_primaries_named,
+ image_description_info_handle_tf_power,
+ image_description_info_handle_tf_named,
+ image_description_info_handle_luminances,
+ image_description_info_handle_target_primaries,
+ image_description_info_handle_target_luminance,
+ image_description_info_handle_target_max_cll,
+ image_description_info_handle_target_max_fall
+};
+
+static void PumpColorspaceEvents(Wayland_ColorInfoState *state)
+{
+ SDL_VideoData *vid = SDL_GetVideoDevice()->internal;
+
+ // Run the image description sequence to completion in its own queue.
+ struct wl_event_queue *queue = WAYLAND_wl_display_create_queue(vid->display);
+ WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description, queue);
+
+ while (state->wp_image_description) {
+ WAYLAND_wl_display_dispatch_queue(vid->display, queue);
+ }
+
+ WAYLAND_wl_event_queue_destroy(queue);
+}
+
+static void image_description_handle_failed(void *data,
+ struct wp_image_description_v1 *wp_image_description_v1,
+ uint32_t cause,
+ const char *msg)
+{
+ Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
+
+ wp_image_description_v1_destroy(state->wp_image_description);
+ state->wp_image_description = NULL;
+}
+
+static void image_description_handle_ready(void *data,
+ struct wp_image_description_v1 *wp_image_description_v1,
+ uint32_t identity)
+{
+ Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
+
+ // This will inherit the queue of the factory image description object.
+ 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);
+}
+
+static const struct wp_image_description_v1_listener image_description_listener = {
+ image_description_handle_failed,
+ image_description_handle_ready
+};
+
+bool Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, Wayland_ColorInfo *info)
+{
+ Wayland_ColorInfoState state;
+ SDL_zero(state);
+ state.info = info;
+
+ 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);
+
+ PumpColorspaceEvents(&state);
+
+ return state.result;
+}
+
+bool Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, Wayland_ColorInfo *info)
+{
+ Wayland_ColorInfoState state;
+ SDL_zero(state);
+ state.info = info;
+
+ 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);
+
+ PumpColorspaceEvents(&state);
+
+ return state.result;
+}
+
+#endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/src/video/wayland/SDL_waylandcolor.h b/src/video/wayland/SDL_waylandcolor.h
new file mode 100644
index 0000000000000..ba6d7f00fa5d9
--- /dev/null
+++ b/src/video/wayland/SDL_waylandcolor.h
@@ -0,0 +1,41 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "SDL_internal.h"
+
+#ifndef SDL_waylandcolor_h_
+#define SDL_waylandcolor_h_
+
+#include "../SDL_sysvideo.h"
+
+typedef struct Wayland_ColorInfo
+{
+ SDL_HDROutputProperties HDR;
+
+ // The ICC fd is only valid if the size is non-zero.
+ int icc_fd;
+ Uint32 icc_size;
+} Wayland_ColorInfo;
+
+extern bool Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, Wayland_ColorInfo *info);
+extern bool Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, Wayland_ColorInfo *info);
+
+#endif // SDL_waylandcolor_h_
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index 2233ab25ec4fd..15fa01b877398 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -27,6 +27,7 @@
#include "../../events/SDL_events_c.h"
#include "SDL_waylandclipboard.h"
+#include "SDL_waylandcolor.h"
#include "SDL_waylandevents_c.h"
#include "SDL_waylandkeyboard.h"
#include "SDL_waylandmessagebox.h"
@@ -63,6 +64,7 @@
#include "xdg-output-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
#include "xdg-toplevel-icon-v1-client-protocol.h"
+#include "color-management-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
@@ -249,7 +251,7 @@ static int SDLCALL Wayland_DisplayPositionCompare(const void *a, const void *b)
* The primary is determined by the following criteria, in order:
* - Landscape is preferred over portrait
* - The highest native resolution
- * - TODO: A higher HDR range is preferred
+ * - A higher HDR range is preferred
* - Higher refresh is preferred (ignoring small differences)
* - Lower scale values are preferred (larger display)
*/
@@ -271,6 +273,7 @@ static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid)
int best_width = 0;
int best_height = 0;
double best_scale = 0.0;
+ float best_headroom = 0.0f;
int best_refresh = 0;
bool best_is_landscape = false;
int best_index = 0;
@@ -286,11 +289,15 @@ static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid)
if (d->pixel_width > best_width || d->pixel_height > best_height) {
have_new_best = true;
} else if (d->pixel_width == best_width && d->pixel_height == best_height) {
- if (d->refresh - best_refresh > REFRESH_DELTA) { // Favor a higher refresh rate, but ignore small differences (e.g. 59.97 vs 60.1)
- have_new_best = true;
- } else if (d->scale_factor < best_scale && SDL_abs(d->refresh - best_refresh) <= REFRESH_DELTA) {
- // Prefer a lower scale display if the difference in refresh rate is small.
+ if (d->HDR.HDR_headroom > best_headroom) { // Favor a higher HDR luminance range
have_new_best = true;
+ } else if (d->HDR.HDR_headroom == best_headroom) {
+ if (d->refresh - best_refresh > REFRESH_DELTA) { // Favor a higher refresh rate, but ignore small differences (e.g. 59.97 vs 60.1)
+ have_new_best = true;
+ } else if (d->scale_factor < best_scale && SDL_abs(d->refresh - best_refresh) <= REFRESH_DELTA) {
+ // Prefer a lower scale display if the difference in refresh rate is small.
+ have_new_best = true;
+ }
}
}
}
@@ -299,6 +306,7 @@ static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid)
best_width = d->pixel_width;
best_height = d->pixel_height;
best_scale = d->scale_factor;
+ best_headroom = d->HDR.HDR_headroom;
best_refresh = d->refresh;
best_is_landscape = is_landscape;
best_index = i;
@@ -630,6 +638,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols)
device->SetWindowIcon = Wayland_SetWindowIcon;
device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels;
device->GetWindowContentScale = Wayland_GetWindowContentScale;
+ device->GetWindowICCProfile = Wayland_GetWindowICCProfile;
device->GetDisplayForWindow = Wayland_GetDisplayForWindow;
device->DestroyWindow = Wayland_DestroyWindow;
device->SetWindowHitTest = Wayland_SetWindowHitTest;
@@ -1050,6 +1059,8 @@ static void display_handle_done(void *data,
AddEmulatedModes(internal, native_mode.w, native_mode.h);
}
+ SDL_SetDisplayHDRProperties(dpy, &internal->HDR);
+
if (internal->display == 0) {
// First time getting display info, initialize the VideoDisplay
if (internal->physical_width_mm >= internal->physical_height_mm) {
@@ -1107,6 +1118,27 @@ static const struct wl_output_listener output_listener = {
display_handle_description // Version 4
};
+static void Wayland_GetOutputColorInfo(SDL_DisplayData *display)
+{
+ Wayland_ColorInfo info;
+ SDL_zero(info);
+
+ if (Wayland_GetColorInfoForOutput(display, &info)) {
+ SDL_copyp(&display->HDR, &info.HDR);
+ }
+}
+
+static void handle_output_image_description_changed(void *data,
+ struct wp_color_management_output_v1 *wp_color_management_output_v1)
+{
+ // wl_display.done is called after this event, so the display HDR status will be updated there.
+ Wayland_GetOutputColorInfo(data);
+}
+
+static const struct wp_color_management_output_v1_listener wp_color_management_output_listener = {
+ handle_output_image_description_changed
+};
+
static bool Wayland_add_display(SDL_VideoData *d, uint32_t id, uint32_t version)
{
struct wl_output *output;
@@ -1136,6 +1168,11 @@ static bool Wayland_add_display(SDL_VideoData *d, uint32_t id, uint32_t version)
data->xdg_output = zxdg_output_manager_v1_get_xdg_output(data->videodata->xdg_output_manager, output);
zxdg_output_v1_add_listener(data->xdg_output, &xdg_output_listener, data);
}
+ if (data->videodata->wp_color_manager_v1) {
+ data->wp_color_management_output = wp_color_manager_v1_get_output(data->videodata->wp_color_manager_v1, output);
+ wp_color_management_output_v1_add_listener(data->wp_color_management_output, &wp_color_management_output_listener, data);
+ Wayland_GetOutputColorInfo(data);
+ }
return true;
}
@@ -1153,6 +1190,10 @@ static void Wayland_free_display(SDL_VideoDisplay *display, bool send_event)
SDL_free(display_data->wl_output_name);
+ if (display_data->wp_color_management_output) {
+ wp_color_management_output_v1_destroy(display_data->wp_color_management_output);
+ }
+
if (display_data->xdg_output) {
zxdg_output_v1_destroy(display_data->xdg_output);
}
@@ -1187,6 +1228,16 @@ static void Wayland_init_xdg_output(SDL_VideoData *d)
}
}
+static void Wayland_InitColorManager(SDL_VideoData *d)
+{
+ for (int i = 0; i < d->output_count; ++i) {
+ SDL_DisplayData *disp = d->output_list[i];
+ disp->wp_color_management_output = wp_color_manager_v1_get_output(disp->videodata->wp_color_manager_v1, disp->output);
+ wp_color_management_output_v1_add_listener(disp->wp_color_management_output, &wp_color_management_output_listener, disp);
+ Wayland_GetOutputColorInfo(disp);
+ }
+}
+
static void handle_ping_xdg_wm_base(void *data, struct xdg_wm_base *xdg, uint32_t serial)
{
xdg_wm_base_pong(xdg, serial);
@@ -1280,6 +1331,9 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
d->xdg_toplevel_icon_manager_v1 = wl_registry_bind(d->registry, id, &xdg_toplevel_icon_manager_v1_interface, 1);
} else if (SDL_strcmp(interface, "frog_color_management_factory_v1") == 0) {
d->frog_color_management_factory_v1 = wl_registry_bind(d->registry, id, &frog_color_management_factory_v1_interface, 1);
+ } else if (SDL_strcmp(interface, "xx_color_manager_v4") == 0) {
+ d->wp_color_manager_v1 = wl_registry_bind(d->registry, id, &wp_color_manager_v1_interface, 1);
+ Wayland_InitColorManager(d);
}
}
@@ -1567,6 +1621,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
data->frog_color_management_factory_v1 = NULL;
}
+ if (data->wp_color_manager_v1) {
+ wp_color_manager_v1_destroy(data->wp_color_manager_v1);
+ data->wp_color_manager_v1 = NULL;
+ }
+
if (data->compositor) {
wl_compositor_destroy(data->compositor);
data->compositor = NULL;
diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h
index 44f5f35980e43..5b5ee24c0fa2c 100644
--- a/src/video/wayland/SDL_waylandvideo.h
+++ b/src/video/wayland/SDL_waylandvideo.h
@@ -83,6 +83,7 @@ struct SDL_VideoData
struct wp_alpha_modifier_v1 *wp_alpha_modifier_v1;
struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1;
struct frog_color_management_factory_v1 *frog_color_management_factory_v1;
+ struct wp_color_manager_v1 *wp_color_manager_v1;
struct zwp_tablet_manager_v2 *tablet_manager;
struct xkb_context *xkb_context;
@@ -102,6 +103,7 @@ struct SDL_DisplayData
SDL_VideoData *videodata;
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
+ struct wp_color_management_output_v1 *wp_color_management_output;
char *wl_output_name;
double scale_factor;
uint32_t registry_id;
@@ -111,6 +113,8 @@ struct SDL_DisplayData
SDL_DisplayOrientation orientation;
int physical_width_mm, physical_height_mm;
bool has_logical_position, has_logical_size;
+ bool running_colorspace_event_queue;
+ SDL_HDROutputProperties HDR;
SDL_DisplayID display;
SDL_VideoDisplay placeholder;
int wl_output_done_count;
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 50bed340f4b60..8dbc5e090a9d1 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -23,6 +23,8 @@
#ifdef SDL_VIDEO_DRIVER_WAYLAND
+#include <sys/mman.h>
+
#include "../SDL_sysvideo.h"
#include "../../events/SDL_events_c.h"
#include "../../core/unix/SDL_appid.h"
@@ -31,6 +33,7 @@
#include "SDL_waylandwindow.h"
#include "SDL_waylandvideo.h"
#include "../../SDL_hints_c.h"
+#include "SDL_waylandcolor.h"
#include "alpha-modifier-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
@@ -43,6 +46,7 @@
#include "xdg-dialog-v1-client-protocol.h"
#include "frog-color-management-v1-client-protocol.h"
#include "xdg-toplevel-icon-v1-client-protocol.h"
+#include "color-management-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
@@ -1647,6 +1651,28 @@ static const struct frog_color_managed_surface_listener frog_surface_listener =
frog_preferred_metadata_handler
};
+static void feedback_surface_preferred_changed(void *data,
+ struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback_v1,
+ uint32_t identity)
+{
+ SDL_WindowData *wind = (SDL_WindowData *)data;
+ Wayland_ColorInfo info;
+ SDL_zero(info);
+
+ if (Wayland_GetColorInfoForWindow(wind, &info)) {
+ SDL_SetWindowHDRProperties(wind->sdlwindow, &info.HDR, true);
+ if (info.icc_size) {
+ wind->icc_fd = info.icc_fd;
+ wind->icc_size = info.icc_size;
+ SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
+ }
+ }
+}
+
+static const struct wp_color_management_surface_feedback_v1_listener color_management_surface_feedback_listener = {
+ feedback_surface_preferred_changed
+};
+
static void SetKeyboardFocus(SDL_Window *window, bool set_focus)
{
SDL_Window *toplevel = window;
@@ -2593,7 +2619,10 @@ bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper
}
if (!custom_surface_role) {
- if (c->frog_color_management_factory_v1) {
+ if (c->wp_color_manager_v1) {
+ data->wp_color_management_surface_feedback = wp_color_manager_v1_get_surface_feedback(c->wp_color_manager_v1, data->surface);
+ wp_color_management_surface_feedback_v1_add_listener(data->wp_color_management_surface_feedback, &color_management_surface_feedback_listener, data);
+ } else if (c->frog_color_management_factory_v1) {
data->frog_color_managed_surface = frog_color_management_factory_v1_get_color_managed_surface(c->frog_color_management_factory_v1, data->surface);
frog_color_managed_surface_add_listener(data->frog_color_managed_surface, &frog_surface_listener, data);
}
@@ -2875,6 +2904,26 @@ bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surfa
return true;
}
+void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size)
+{
+ SDL_WindowData *wind = window->internal;
+ void *ret = NULL;
+
+ if (wind->icc_size > 0) {
+ void *icc_map = mmap(NULL, wind->icc_size, PROT_READ, MAP_PRIVATE, wind->icc_fd, 0);
+ if (icc_map != MAP_FAILED) {
+ ret = SDL_malloc(wind->icc_size);
+ if (ret) {
+ *size = wind->icc_size;
+ SDL_memcpy(ret, icc_map, *size);
+ }
+ munmap(icc_map, wind->icc_size);
+ }
+ }
+
+ return ret;
+}
+
bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_WindowData *wind = window->internal;
@@ -2994,6 +3043,10 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
frog_color_managed_surface_destroy(wind->frog_color_managed_surface);
}
+ if (wind->wp_color_management_surface_feedback) {
+ wp_color_management_surface_feedback_v1_destroy(wind->wp_color_management_surface_feedback);
+ }
+
SDL_free(wind->outputs);
SDL_free(wind->app_id);
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index 906c5e448dbe5..bd854226f1149 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -116,6 +116,7 @@ struct SDL_WindowData
struct wp_alpha_modifier_surface_v1 *wp_alpha_modifier_surface_v1;
struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1;
struct frog_color_managed_surface *frog_color_managed_surface;
+ struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback;
SDL_AtomicInt swap_interval_ready;
@@ -184,6 +185,8 @@ struct SDL_WindowData
int fullscreen_deadline_count;
int maximized_restored_deadline_count;
Uint64 last_focus_event_time_ns;
+ int icc_fd;
+ Uint32 icc_size;
bool floating;
bool suspended;
bool resizing;
@@ -231,6 +234,7 @@ extern void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
extern bool Wayland_SuspendScreenSaver(SDL_VideoDevice *_this);
extern bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon);
extern float Wayland_GetWindowContentScale(SDL_VideoDevice *_this, SDL_Window *window);
+extern void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size);
extern bool Wayland_SetWindowHitTest(SDL_Window *window, bool enabled);
extern bool Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);
diff --git a/wayland-protocols/color-management-v1.xml b/wayland-protocols/color-management-v1.xml
new file mode 100644
index 0000000000000..7f8da78f1238d
--- /dev/null
+++ b/wayland-protocols/color-management-v1.xml
@@ -0,0 +1,1631 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="color_management_v1">
+ <copyright>
+ Copyright 2019 Sebastian Wick
+ Copyright 2019 Erwin Burema
+ Copyright 2020 AMD
+ Copyright 2020-2024 Collabora, Ltd.
+ Copyright 2024 Xaver Hugl
+ Copyright 2022-2025 Red Hat, Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="color management protocol">
+ The aim of the color management extension is to allow clients to know
+ the color properties of outputs, and to tell the compositor about the color
+ properties of their content on surfaces. Doing this enables a compositor
+ to perform automatic color management of content for different outputs
+ according to how content is intended to look like.
+
+ The color properties are represented as an image description object which
+ is immutable after it has been created. A wl_output always has an
+ associated image description that clients can observe. A wl_surface
+ always has an associated preferred image description as a hint chosen by
+ the compositor that clients can also observe. Clients can set an image
+ description on a wl_surface to denote the color characteristics of the
+ surface contents.
+
+ An image description includes SDR and HDR colorimetry and encoding, HDR
+ metadata, and viewing environment parameters. An image description does
+ not include the properties set through color-representation extension.
+ It is expected that the color-representation extension is used in
+ conjunction with the color management extension when necessary,
+ particularly with the YUV family of pixel formats.
+
+ Recommendation ITU-T H.273
+ "Coding-independent code points for video signal type identification"
+ shall be referred to as simply H.273 here.
+
+ The color-and-hdr repository
+ (https://gitlab.freedesktop.org/pq/color-and-hdr) contains
+ background information on the protocol design and legacy color management.
+ It also contains a glossary, learning resources for digital color, tools,
+ samples and more.
+
+ The terminology used in this protocol is based on common color science and
+ color encoding terminology where possible. The glossary in the color-and-hdr
+ repository shall be the authority on the definition of terms in this
+ protocol.
+
+ Warning! The protocol described in this file is currently in the testing
+ phase. Backward compatible changes may be added together with the
+ corresponding interface version bump. Backward incompatible changes can
+ only be done by creating a new major version of the extension.
+ </description>
+
+ <interface name="wp_color_manager_v1" version="1">
+ <description summary="color manager singleton">
+ A singleton global interface used for getting color management extensions
+ for wl_surface and wl_output objects, and for creating client defined
+ image description objects. The extension interfaces allow
+ getting the image description of outputs and setting the image
+ description of surfaces.
+
+ Compositors should never remove this global.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description sum
(Patch may be truncated, please check the link at the top of this post.)