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 | |
32 | typedef 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 | |
58 | static 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 | |
72 | void 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 | |
90 | static 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 | |
113 | static 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 | |
123 | static 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 | |
133 | static 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 | |
140 | static 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 | |
147 | static 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 | |
154 | static 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 | |
164 | static 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 | |
174 | static 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 | |
182 | static 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 | |
189 | static 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 | |
196 | static 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 | |
210 | static 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 | |
230 | static 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 | |
243 | static 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 | |
258 | static const struct wp_image_description_v1_listener image_description_listener = { |
259 | image_description_handle_failed, |
260 | image_description_handle_ready |
261 | }; |
262 | |
263 | void 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 | |
283 | void 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 | |