1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_VIDEO_DRIVER_WAYLAND
25
26#include "SDL_waylandcolor.h"
27#include "SDL_waylandvideo.h"
28#include "SDL_waylandwindow.h"
29#include "color-management-v1-client-protocol.h"
30#include "../../events/SDL_windowevents_c.h"
31
32typedef struct Wayland_ColorInfoState
33{
34 struct wp_image_description_v1 *wp_image_description;
35 struct wp_image_description_info_v1 *wp_image_description_info;
36
37 union
38 {
39 SDL_WindowData *window_data;
40 SDL_DisplayData *display_data;
41 };
42
43 enum
44 {
45 WAYLAND_COLOR_OBJECT_TYPE_WINDOW,
46 WAYLAND_COLOR_OBJECT_TYPE_DISPLAY
47 } object_type;
48
49 SDL_HDROutputProperties HDR;
50
51 // The ICC fd is only valid if the size is non-zero.
52 int icc_fd;
53 Uint32 icc_size;
54
55 bool deferred_event_processing;
56} Wayland_ColorInfoState;
57
58static void Wayland_CancelColorInfoRequest(Wayland_ColorInfoState *state)
59{
60 if (state) {
61 if (state->wp_image_description_info) {
62 wp_image_description_info_v1_destroy(state->wp_image_description_info);
63 state->wp_image_description_info = NULL;
64 }
65 if (state->wp_image_description) {
66 wp_image_description_v1_destroy(state->wp_image_description);
67 state->wp_image_description = NULL;
68 }
69 }
70}
71
72void Wayland_FreeColorInfoState(Wayland_ColorInfoState *state)
73{
74 if (state) {
75 Wayland_CancelColorInfoRequest(state);
76
77 switch (state->object_type) {
78 case WAYLAND_COLOR_OBJECT_TYPE_WINDOW:
79 state->window_data->color_info_state = NULL;
80 break;
81 case WAYLAND_COLOR_OBJECT_TYPE_DISPLAY:
82 state->display_data->color_info_state = NULL;
83 break;
84 }
85
86 SDL_free(state);
87 }
88}
89
90static void image_description_info_handle_done(void *data,
91 struct wp_image_description_info_v1 *wp_image_description_info_v1)
92{
93 Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
94 Wayland_CancelColorInfoRequest(state);
95
96 switch (state->object_type) {
97 case WAYLAND_COLOR_OBJECT_TYPE_WINDOW:
98 {
99 SDL_SetWindowHDRProperties(state->window_data->sdlwindow, &state->HDR, true);
100 if (state->icc_size) {
101 state->window_data->icc_fd = state->icc_fd;
102 state->window_data->icc_size = state->icc_size;
103 SDL_SendWindowEvent(state->window_data->sdlwindow, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
104 }
105 } break;
106 case WAYLAND_COLOR_OBJECT_TYPE_DISPLAY:
107 {
108 SDL_copyp(&state->display_data->HDR, &state->HDR);
109 } break;
110 }
111}
112
113static void image_description_info_handle_icc_file(void *data,
114 struct wp_image_description_info_v1 *wp_image_description_info_v1,
115 int32_t icc, uint32_t icc_size)
116{
117 Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
118
119 state->icc_fd = icc;
120 state->icc_size = icc_size;
121}
122
123static void image_description_info_handle_primaries(void *data,
124 struct wp_image_description_info_v1 *wp_image_description_info_v1,
125 int32_t r_x, int32_t r_y,
126 int32_t g_x, int32_t g_y,
127 int32_t b_x, int32_t b_y,
128 int32_t w_x, int32_t w_y)
129{
130 // NOP
131}
132
133static void image_description_info_handle_primaries_named(void *data,
134 struct wp_image_description_info_v1 *wp_image_description_info_v1,
135 uint32_t primaries)
136{
137 // NOP
138}
139
140static void image_description_info_handle_tf_power(void *data,
141 struct wp_image_description_info_v1 *wp_image_description_info_v1,
142 uint32_t eexp)
143{
144 // NOP
145}
146
147static void image_description_info_handle_tf_named(void *data,
148 struct wp_image_description_info_v1 *wp_image_description_info_v1,
149 uint32_t tf)
150{
151 // NOP
152}
153
154static void image_description_info_handle_luminances(void *data,
155 struct wp_image_description_info_v1 *wp_image_description_info_v1,
156 uint32_t min_lum,
157 uint32_t max_lum,
158 uint32_t reference_lum)
159{
160 Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
161 state->HDR.HDR_headroom = (float)max_lum / (float)reference_lum;
162}
163
164static void image_description_info_handle_target_primaries(void *data,
165 struct wp_image_description_info_v1 *wp_image_description_info_v1,
166 int32_t r_x, int32_t r_y,
167 int32_t g_x, int32_t g_y,
168 int32_t b_x, int32_t b_y,
169 int32_t w_x, int32_t w_y)
170{
171 // NOP
172}
173
174static void image_description_info_handle_target_luminance(void *data,
175 struct wp_image_description_info_v1 *wp_image_description_info_v1,
176 uint32_t min_lum,
177 uint32_t max_lum)
178{
179 // NOP
180}
181
182static void image_description_info_handle_target_max_cll(void *data,
183 struct wp_image_description_info_v1 *wp_image_description_info_v1,
184 uint32_t max_cll)
185{
186 // NOP
187}
188
189static void image_description_info_handle_target_max_fall(void *data,
190 struct wp_image_description_info_v1 *wp_image_description_info_v1,
191 uint32_t max_fall)
192{
193 // NOP
194}
195
196static const struct wp_image_description_info_v1_listener image_description_info_listener = {
197 image_description_info_handle_done,
198 image_description_info_handle_icc_file,
199 image_description_info_handle_primaries,
200 image_description_info_handle_primaries_named,
201 image_description_info_handle_tf_power,
202 image_description_info_handle_tf_named,
203 image_description_info_handle_luminances,
204 image_description_info_handle_target_primaries,
205 image_description_info_handle_target_luminance,
206 image_description_info_handle_target_max_cll,
207 image_description_info_handle_target_max_fall
208};
209
210static void PumpColorspaceEvents(Wayland_ColorInfoState *state)
211{
212 SDL_VideoData *vid = SDL_GetVideoDevice()->internal;
213
214 // Run the image description sequence to completion in its own queue.
215 struct wl_event_queue *queue = WAYLAND_wl_display_create_queue(vid->display);
216 if (state->deferred_event_processing) {
217 WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description_info, queue);
218 } else {
219 WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description, queue);
220 }
221
222 while (state->wp_image_description) {
223 WAYLAND_wl_display_dispatch_queue(vid->display, queue);
224 }
225
226 WAYLAND_wl_event_queue_destroy(queue);
227 Wayland_FreeColorInfoState(state);
228}
229
230static void image_description_handle_failed(void *data,
231 struct wp_image_description_v1 *wp_image_description_v1,
232 uint32_t cause,
233 const char *msg)
234{
235 Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
236 Wayland_CancelColorInfoRequest(state);
237
238 if (state->deferred_event_processing) {
239 Wayland_FreeColorInfoState(state);
240 }
241}
242
243static void image_description_handle_ready(void *data,
244 struct wp_image_description_v1 *wp_image_description_v1,
245 uint32_t identity)
246{
247 Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
248
249 // This will inherit the queue of the factory image description object.
250 state->wp_image_description_info = wp_image_description_v1_get_information(state->wp_image_description);
251 wp_image_description_info_v1_add_listener(state->wp_image_description_info, &image_description_info_listener, data);
252
253 if (state->deferred_event_processing) {
254 PumpColorspaceEvents(state);
255 }
256}
257
258static const struct wp_image_description_v1_listener image_description_listener = {
259 image_description_handle_failed,
260 image_description_handle_ready
261};
262
263void Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, bool defer_event_processing)
264{
265 // Cancel any pending request, as it is out-of-date.
266 Wayland_FreeColorInfoState(window_data->color_info_state);
267 Wayland_ColorInfoState *state = SDL_calloc(1, sizeof(Wayland_ColorInfoState));
268
269 if (state) {
270 window_data->color_info_state = state;
271 state->window_data = window_data;
272 state->object_type = WAYLAND_COLOR_OBJECT_TYPE_WINDOW;
273 state->deferred_event_processing = defer_event_processing;
274 state->wp_image_description = wp_color_management_surface_feedback_v1_get_preferred(window_data->wp_color_management_surface_feedback);
275 wp_image_description_v1_add_listener(state->wp_image_description, &image_description_listener, state);
276
277 if (!defer_event_processing) {
278 PumpColorspaceEvents(state);
279 }
280 }
281}
282
283void Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, bool defer_event_processing)
284{
285 // Cancel any pending request, as it is out-of-date.
286 Wayland_FreeColorInfoState(display_data->color_info_state);
287 Wayland_ColorInfoState *state = SDL_calloc(1, sizeof(Wayland_ColorInfoState));
288
289 if (state) {
290 display_data->color_info_state = state;
291 state->display_data = display_data;
292 state->object_type = WAYLAND_COLOR_OBJECT_TYPE_DISPLAY;
293 state->deferred_event_processing = defer_event_processing;
294 state->wp_image_description = wp_color_management_output_v1_get_image_description(display_data->wp_color_management_output);
295 wp_image_description_v1_add_listener(state->wp_image_description, &image_description_listener, state);
296
297 if (!defer_event_processing) {
298 PumpColorspaceEvents(state);
299 }
300 }
301}
302
303#endif // SDL_VIDEO_DRIVER_WAYLAND
304