summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/wayland
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/wayland')
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandclipboard.c174
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandclipboard.h34
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandcolor.c303
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandcolor.h35
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddatamanager.c619
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddatamanager.h162
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddyn.c176
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddyn.h195
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandevents.c3408
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandevents_c.h195
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandkeyboard.c209
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandkeyboard.h40
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmessagebox.c196
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmessagebox.h31
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmouse.c1057
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmouse.h34
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandopengles.c220
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandopengles.h48
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandshmbuffer.c170
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandshmbuffer.h38
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandsym.h242
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvideo.c1692
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvideo.h140
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvulkan.c203
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvulkan.h55
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.c3125
-rw-r--r--contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.h248
27 files changed, 13049 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandclipboard.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandclipboard.c
new file mode 100644
index 0000000..6218733
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandclipboard.c
@@ -0,0 +1,174 @@
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#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_WAYLAND
24
25#include "SDL_waylanddatamanager.h"
26#include "SDL_waylandevents_c.h"
27#include "SDL_waylandclipboard.h"
28#include "../SDL_clipboard_c.h"
29#include "../../events/SDL_events_c.h"
30
31
32bool Wayland_SetClipboardData(SDL_VideoDevice *_this)
33{
34 SDL_VideoData *video_data = _this->internal;
35 SDL_WaylandDataDevice *data_device = NULL;
36 bool result = true;
37
38 if (video_data->input && video_data->input->data_device) {
39 data_device = video_data->input->data_device;
40
41 if (_this->clipboard_callback && _this->clipboard_mime_types) {
42 SDL_WaylandDataSource *source = Wayland_data_source_create(_this);
43 Wayland_data_source_set_callback(source, _this->clipboard_callback, _this->clipboard_userdata, _this->clipboard_sequence);
44
45 result = Wayland_data_device_set_selection(data_device, source, (const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types);
46 if (!result) {
47 Wayland_data_source_destroy(source);
48 }
49 } else {
50 result = Wayland_data_device_clear_selection(data_device);
51 }
52 }
53
54 return result;
55}
56
57void *Wayland_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *length)
58{
59 SDL_VideoData *video_data = _this->internal;
60 SDL_WaylandDataDevice *data_device = NULL;
61 void *buffer = NULL;
62
63 if (video_data->input && video_data->input->data_device) {
64 data_device = video_data->input->data_device;
65 if (data_device->selection_source) {
66 buffer = SDL_GetInternalClipboardData(_this, mime_type, length);
67 } else if (Wayland_data_offer_has_mime(data_device->selection_offer, mime_type)) {
68 buffer = Wayland_data_offer_receive(data_device->selection_offer, mime_type, length);
69 }
70 }
71
72 return buffer;
73}
74
75bool Wayland_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
76{
77 SDL_VideoData *video_data = _this->internal;
78 SDL_WaylandDataDevice *data_device = NULL;
79 bool result = false;
80
81 if (video_data->input && video_data->input->data_device) {
82 data_device = video_data->input->data_device;
83 if (data_device->selection_source) {
84 result = SDL_HasInternalClipboardData(_this, mime_type);
85 } else {
86 result = Wayland_data_offer_has_mime(data_device->selection_offer, mime_type);
87 }
88 }
89 return result;
90}
91
92static const char *text_mime_types[] = {
93 TEXT_MIME,
94 "text/plain",
95 "TEXT",
96 "UTF8_STRING",
97 "STRING"
98};
99
100const char **Wayland_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types)
101{
102 *num_mime_types = SDL_arraysize(text_mime_types);
103 return text_mime_types;
104}
105
106bool Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text)
107{
108 SDL_VideoData *video_data = _this->internal;
109 SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
110 bool result;
111
112 if (video_data->input && video_data->input->primary_selection_device) {
113 primary_selection_device = video_data->input->primary_selection_device;
114 if (text[0] != '\0') {
115 SDL_WaylandPrimarySelectionSource *source = Wayland_primary_selection_source_create(_this);
116 Wayland_primary_selection_source_set_callback(source, SDL_ClipboardTextCallback, SDL_strdup(text));
117
118 result = Wayland_primary_selection_device_set_selection(primary_selection_device,
119 source,
120 text_mime_types,
121 SDL_arraysize(text_mime_types));
122 if (!result) {
123 Wayland_primary_selection_source_destroy(source);
124 }
125 } else {
126 result = Wayland_primary_selection_device_clear_selection(primary_selection_device);
127 }
128 } else {
129 result = SDL_SetError("Primary selection not supported");
130 }
131 return result;
132}
133
134char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this)
135{
136 SDL_VideoData *video_data = _this->internal;
137 SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
138 char *text = NULL;
139 size_t length = 0;
140
141 if (video_data->input && video_data->input->primary_selection_device) {
142 primary_selection_device = video_data->input->primary_selection_device;
143 if (primary_selection_device->selection_source) {
144 text = Wayland_primary_selection_source_get_data(primary_selection_device->selection_source, TEXT_MIME, &length);
145 } else if (Wayland_primary_selection_offer_has_mime(primary_selection_device->selection_offer, TEXT_MIME)) {
146 text = Wayland_primary_selection_offer_receive(primary_selection_device->selection_offer, TEXT_MIME, &length);
147 }
148 }
149
150 if (!text) {
151 text = SDL_strdup("");
152 }
153
154 return text;
155}
156
157bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this)
158{
159 SDL_VideoData *video_data = _this->internal;
160 SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
161 bool result = false;
162
163 if (video_data->input && video_data->input->primary_selection_device) {
164 primary_selection_device = video_data->input->primary_selection_device;
165 if (primary_selection_device->selection_source) {
166 result = true;
167 } else {
168 result = Wayland_primary_selection_offer_has_mime(primary_selection_device->selection_offer, TEXT_MIME);
169 }
170 }
171 return result;
172}
173
174#endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandclipboard.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandclipboard.h
new file mode 100644
index 0000000..ed6546f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandclipboard.h
@@ -0,0 +1,34 @@
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#include "SDL_internal.h"
22
23#ifndef SDL_waylandclipboard_h_
24#define SDL_waylandclipboard_h_
25
26extern const char **Wayland_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types);
27extern bool Wayland_SetClipboardData(SDL_VideoDevice *_this);
28extern void *Wayland_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *length);
29extern bool Wayland_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type);
30extern bool Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text);
31extern char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this);
32extern bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this);
33
34#endif // SDL_waylandclipboard_h_
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandcolor.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandcolor.c
new file mode 100644
index 0000000..dfc69bb
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandcolor.c
@@ -0,0 +1,303 @@
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
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandcolor.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandcolor.h
new file mode 100644
index 0000000..bef5d8c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandcolor.h
@@ -0,0 +1,35 @@
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#ifndef SDL_waylandcolor_h_
25#define SDL_waylandcolor_h_
26
27#include "../SDL_sysvideo.h"
28
29struct Wayland_ColorInfoState;
30
31extern void Wayland_FreeColorInfoState(struct Wayland_ColorInfoState *state);
32extern void Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, bool defer_event_processing);
33extern void Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, bool defer_event_processing);
34
35#endif // SDL_waylandcolor_h_
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddatamanager.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddatamanager.c
new file mode 100644
index 0000000..6f5b32b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddatamanager.c
@@ -0,0 +1,619 @@
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 <fcntl.h>
27#include <unistd.h>
28#include <limits.h>
29#include <signal.h>
30
31#include "../../core/unix/SDL_poll.h"
32#include "../../events/SDL_events_c.h"
33#include "../SDL_clipboard_c.h"
34
35#include "SDL_waylandvideo.h"
36#include "SDL_waylanddatamanager.h"
37#include "primary-selection-unstable-v1-client-protocol.h"
38
39/* FIXME: This is arbitrary, but we want this to be less than a frame because
40 * any longer can potentially spin an infinite loop of PumpEvents (!)
41 */
42#define PIPE_TIMEOUT_NS SDL_MS_TO_NS(14)
43
44static ssize_t write_pipe(int fd, const void *buffer, size_t total_length, size_t *pos)
45{
46 int ready = 0;
47 ssize_t bytes_written = 0;
48 ssize_t length = total_length - *pos;
49
50 sigset_t sig_set;
51 sigset_t old_sig_set;
52 struct timespec zerotime = { 0 };
53
54 ready = SDL_IOReady(fd, SDL_IOR_WRITE, PIPE_TIMEOUT_NS);
55
56 sigemptyset(&sig_set);
57 sigaddset(&sig_set, SIGPIPE);
58
59#ifdef SDL_THREADS_DISABLED
60 sigprocmask(SIG_BLOCK, &sig_set, &old_sig_set);
61#else
62 pthread_sigmask(SIG_BLOCK, &sig_set, &old_sig_set);
63#endif
64
65 if (ready == 0) {
66 bytes_written = SDL_SetError("Pipe timeout");
67 } else if (ready < 0) {
68 bytes_written = SDL_SetError("Pipe select error");
69 } else {
70 if (length > 0) {
71 bytes_written = write(fd, (Uint8 *)buffer + *pos, SDL_min(length, PIPE_BUF));
72 }
73
74 if (bytes_written > 0) {
75 *pos += bytes_written;
76 }
77 }
78
79 sigtimedwait(&sig_set, 0, &zerotime);
80
81#ifdef SDL_THREADS_DISABLED
82 sigprocmask(SIG_SETMASK, &old_sig_set, NULL);
83#else
84 pthread_sigmask(SIG_SETMASK, &old_sig_set, NULL);
85#endif
86
87 return bytes_written;
88}
89
90static ssize_t read_pipe(int fd, void **buffer, size_t *total_length)
91{
92 int ready = 0;
93 void *output_buffer = NULL;
94 char temp[PIPE_BUF];
95 size_t new_buffer_length = 0;
96 ssize_t bytes_read = 0;
97 size_t pos = 0;
98
99 ready = SDL_IOReady(fd, SDL_IOR_READ, PIPE_TIMEOUT_NS);
100
101 if (ready == 0) {
102 bytes_read = SDL_SetError("Pipe timeout");
103 } else if (ready < 0) {
104 bytes_read = SDL_SetError("Pipe select error");
105 } else {
106 bytes_read = read(fd, temp, sizeof(temp));
107 }
108
109 if (bytes_read > 0) {
110 pos = *total_length;
111 *total_length += bytes_read;
112
113 new_buffer_length = *total_length + sizeof(Uint32);
114
115 if (!*buffer) {
116 output_buffer = SDL_malloc(new_buffer_length);
117 } else {
118 output_buffer = SDL_realloc(*buffer, new_buffer_length);
119 }
120
121 if (!output_buffer) {
122 bytes_read = -1;
123 } else {
124 SDL_memcpy((Uint8 *)output_buffer + pos, temp, bytes_read);
125 SDL_memset((Uint8 *)output_buffer + (new_buffer_length - sizeof(Uint32)), 0, sizeof(Uint32));
126
127 *buffer = output_buffer;
128 }
129 }
130
131 return bytes_read;
132}
133
134static SDL_MimeDataList *mime_data_list_find(struct wl_list *list,
135 const char *mime_type)
136{
137 SDL_MimeDataList *found = NULL;
138
139 SDL_MimeDataList *mime_list = NULL;
140 wl_list_for_each (mime_list, list, link) {
141 if (SDL_strcmp(mime_list->mime_type, mime_type) == 0) {
142 found = mime_list;
143 break;
144 }
145 }
146 return found;
147}
148
149static bool mime_data_list_add(struct wl_list *list,
150 const char *mime_type,
151 const void *buffer, size_t length)
152{
153 bool result = true;
154 size_t mime_type_length = 0;
155 SDL_MimeDataList *mime_data = NULL;
156 void *internal_buffer = NULL;
157
158 if (buffer) {
159 internal_buffer = SDL_malloc(length);
160 if (!internal_buffer) {
161 return false;
162 }
163 SDL_memcpy(internal_buffer, buffer, length);
164 }
165
166 mime_data = mime_data_list_find(list, mime_type);
167
168 if (!mime_data) {
169 mime_data = SDL_calloc(1, sizeof(*mime_data));
170 if (!mime_data) {
171 result = false;
172 } else {
173 WAYLAND_wl_list_insert(list, &(mime_data->link));
174
175 mime_type_length = SDL_strlen(mime_type) + 1;
176 mime_data->mime_type = SDL_malloc(mime_type_length);
177 if (!mime_data->mime_type) {
178 result = false;
179 } else {
180 SDL_memcpy(mime_data->mime_type, mime_type, mime_type_length);
181 }
182 }
183 }
184
185 if (mime_data && buffer && length > 0) {
186 if (mime_data->data) {
187 SDL_free(mime_data->data);
188 }
189 mime_data->data = internal_buffer;
190 mime_data->length = length;
191 } else {
192 SDL_free(internal_buffer);
193 }
194
195 return result;
196}
197
198static void mime_data_list_free(struct wl_list *list)
199{
200 SDL_MimeDataList *mime_data = NULL;
201 SDL_MimeDataList *next = NULL;
202
203 wl_list_for_each_safe (mime_data, next, list, link) {
204 if (mime_data->data) {
205 SDL_free(mime_data->data);
206 }
207 if (mime_data->mime_type) {
208 SDL_free(mime_data->mime_type);
209 }
210 SDL_free(mime_data);
211 }
212}
213
214static size_t Wayland_send_data(const void *data, size_t length, int fd)
215{
216 size_t result = 0;
217
218 if (length > 0 && data) {
219 while (write_pipe(fd, data, length, &result) > 0) {
220 // Just keep spinning
221 }
222 }
223 close(fd);
224
225 return result;
226}
227
228ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source, const char *mime_type, int fd)
229{
230 const void *data = NULL;
231 size_t length = 0;
232
233 if (source->callback) {
234 data = source->callback(source->userdata.data, mime_type, &length);
235 }
236
237 return Wayland_send_data(data, length, fd);
238}
239
240ssize_t Wayland_primary_selection_source_send(SDL_WaylandPrimarySelectionSource *source, const char *mime_type, int fd)
241{
242 const void *data = NULL;
243 size_t length = 0;
244
245 if (source->callback) {
246 data = source->callback(source->userdata.data, mime_type, &length);
247 }
248
249 return Wayland_send_data(data, length, fd);
250}
251
252void Wayland_data_source_set_callback(SDL_WaylandDataSource *source,
253 SDL_ClipboardDataCallback callback,
254 void *userdata,
255 Uint32 sequence)
256{
257 if (source) {
258 source->callback = callback;
259 source->userdata.sequence = sequence;
260 source->userdata.data = userdata;
261 }
262}
263
264void Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSource *source,
265 SDL_ClipboardDataCallback callback,
266 void *userdata)
267{
268 if (source) {
269 source->callback = callback;
270 source->userdata.sequence = 0;
271 source->userdata.data = userdata;
272 }
273}
274
275static void *Wayland_clone_data_buffer(const void *buffer, const size_t *len)
276{
277 void *clone = NULL;
278 if (*len > 0 && buffer) {
279 clone = SDL_malloc((*len)+sizeof(Uint32));
280 if (clone) {
281 SDL_memcpy(clone, buffer, *len);
282 SDL_memset((Uint8 *)clone + *len, 0, sizeof(Uint32));
283 }
284 }
285 return clone;
286}
287
288void *Wayland_data_source_get_data(SDL_WaylandDataSource *source,
289 const char *mime_type, size_t *length)
290{
291 void *buffer = NULL;
292 const void *internal_buffer;
293 *length = 0;
294
295 if (!source) {
296 SDL_SetError("Invalid data source");
297 } else if (source->callback) {
298 internal_buffer = source->callback(source->userdata.data, mime_type, length);
299 buffer = Wayland_clone_data_buffer(internal_buffer, length);
300 }
301
302 return buffer;
303}
304
305void *Wayland_primary_selection_source_get_data(SDL_WaylandPrimarySelectionSource *source,
306 const char *mime_type, size_t *length)
307{
308 void *buffer = NULL;
309 const void *internal_buffer;
310 *length = 0;
311
312 if (!source) {
313 SDL_SetError("Invalid primary selection source");
314 } else if (source->callback) {
315 internal_buffer = source->callback(source->userdata.data, mime_type, length);
316 buffer = Wayland_clone_data_buffer(internal_buffer, length);
317 }
318
319 return buffer;
320}
321
322void Wayland_data_source_destroy(SDL_WaylandDataSource *source)
323{
324 if (source) {
325 SDL_WaylandDataDevice *data_device = (SDL_WaylandDataDevice *)source->data_device;
326 if (data_device && (data_device->selection_source == source)) {
327 data_device->selection_source = NULL;
328 }
329 wl_data_source_destroy(source->source);
330 if (source->userdata.sequence) {
331 SDL_CancelClipboardData(source->userdata.sequence);
332 } else {
333 SDL_free(source->userdata.data);
334 }
335 SDL_free(source);
336 }
337}
338
339void Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource *source)
340{
341 if (source) {
342 SDL_WaylandPrimarySelectionDevice *primary_selection_device = (SDL_WaylandPrimarySelectionDevice *)source->primary_selection_device;
343 if (primary_selection_device && (primary_selection_device->selection_source == source)) {
344 primary_selection_device->selection_source = NULL;
345 }
346 zwp_primary_selection_source_v1_destroy(source->source);
347 if (source->userdata.sequence == 0) {
348 SDL_free(source->userdata.data);
349 }
350 SDL_free(source);
351 }
352}
353
354void *Wayland_data_offer_receive(SDL_WaylandDataOffer *offer,
355 const char *mime_type, size_t *length)
356{
357 SDL_WaylandDataDevice *data_device = NULL;
358
359 int pipefd[2];
360 void *buffer = NULL;
361 *length = 0;
362
363 if (!offer) {
364 SDL_SetError("Invalid data offer");
365 return NULL;
366 }
367 data_device = offer->data_device;
368 if (!data_device) {
369 SDL_SetError("Data device not initialized");
370 } else if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1) {
371 SDL_SetError("Could not read pipe");
372 } else {
373 wl_data_offer_receive(offer->offer, mime_type, pipefd[1]);
374
375 // TODO: Needs pump and flush?
376 WAYLAND_wl_display_flush(data_device->video_data->display);
377
378 close(pipefd[1]);
379
380 while (read_pipe(pipefd[0], &buffer, length) > 0) {
381 }
382 close(pipefd[0]);
383 }
384 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
385 ". In Wayland_data_offer_receive for '%s', buffer (%zu) at %p",
386 mime_type, *length, buffer);
387 return buffer;
388}
389
390void *Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelectionOffer *offer,
391 const char *mime_type, size_t *length)
392{
393 SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
394
395 int pipefd[2];
396 void *buffer = NULL;
397 *length = 0;
398
399 if (!offer) {
400 SDL_SetError("Invalid data offer");
401 return NULL;
402 }
403 primary_selection_device = offer->primary_selection_device;
404 if (!primary_selection_device) {
405 SDL_SetError("Primary selection device not initialized");
406 } else if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1) {
407 SDL_SetError("Could not read pipe");
408 } else {
409 zwp_primary_selection_offer_v1_receive(offer->offer, mime_type, pipefd[1]);
410
411 // TODO: Needs pump and flush?
412 WAYLAND_wl_display_flush(primary_selection_device->video_data->display);
413
414 close(pipefd[1]);
415
416 while (read_pipe(pipefd[0], &buffer, length) > 0) {
417 }
418 close(pipefd[0]);
419 }
420 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
421 ". In Wayland_primary_selection_offer_receive for '%s', buffer (%zu) at %p",
422 mime_type, *length, buffer);
423 return buffer;
424}
425
426bool Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer,
427 const char *mime_type)
428{
429 return mime_data_list_add(&offer->mimes, mime_type, NULL, 0);
430}
431
432bool Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer *offer,
433 const char *mime_type)
434{
435 return mime_data_list_add(&offer->mimes, mime_type, NULL, 0);
436}
437
438bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer,
439 const char *mime_type)
440{
441 bool found = false;
442
443 if (offer) {
444 found = mime_data_list_find(&offer->mimes, mime_type) != NULL;
445 }
446 return found;
447}
448
449bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *offer,
450 const char *mime_type)
451{
452 bool found = false;
453
454 if (offer) {
455 found = mime_data_list_find(&offer->mimes, mime_type) != NULL;
456 }
457 return found;
458}
459
460void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer)
461{
462 if (offer) {
463 wl_data_offer_destroy(offer->offer);
464 mime_data_list_free(&offer->mimes);
465 SDL_free(offer);
466 }
467}
468
469void Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionOffer *offer)
470{
471 if (offer) {
472 zwp_primary_selection_offer_v1_destroy(offer->offer);
473 mime_data_list_free(&offer->mimes);
474 SDL_free(offer);
475 }
476}
477
478bool Wayland_data_device_clear_selection(SDL_WaylandDataDevice *data_device)
479{
480 bool result = true;
481
482 if (!data_device || !data_device->data_device) {
483 result = SDL_SetError("Invalid Data Device");
484 } else if (data_device->selection_source) {
485 wl_data_device_set_selection(data_device->data_device, NULL, 0);
486 Wayland_data_source_destroy(data_device->selection_source);
487 data_device->selection_source = NULL;
488 }
489 return result;
490}
491
492bool Wayland_primary_selection_device_clear_selection(SDL_WaylandPrimarySelectionDevice *primary_selection_device)
493{
494 bool result = true;
495
496 if (!primary_selection_device || !primary_selection_device->primary_selection_device) {
497 result = SDL_SetError("Invalid Primary Selection Device");
498 } else if (primary_selection_device->selection_source) {
499 zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
500 NULL, 0);
501 Wayland_primary_selection_source_destroy(primary_selection_device->selection_source);
502 primary_selection_device->selection_source = NULL;
503 }
504 return result;
505}
506
507bool Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
508 SDL_WaylandDataSource *source,
509 const char **mime_types,
510 size_t mime_count)
511{
512 bool result = true;
513
514 if (!data_device) {
515 result = SDL_SetError("Invalid Data Device");
516 } else if (!source) {
517 result = SDL_SetError("Invalid source");
518 } else {
519 size_t index = 0;
520 const char *mime_type;
521
522 for (index = 0; index < mime_count; ++index) {
523 mime_type = mime_types[index];
524 wl_data_source_offer(source->source,
525 mime_type);
526 }
527
528 if (index == 0) {
529 Wayland_data_device_clear_selection(data_device);
530 result = SDL_SetError("No mime data");
531 } else {
532 // Only set if there is a valid serial if not set it later
533 if (data_device->selection_serial != 0) {
534 wl_data_device_set_selection(data_device->data_device,
535 source->source,
536 data_device->selection_serial);
537 }
538 if (data_device->selection_source) {
539 Wayland_data_source_destroy(data_device->selection_source);
540 }
541 data_device->selection_source = source;
542 source->data_device = data_device;
543 }
544 }
545
546 return result;
547}
548
549bool Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDevice *primary_selection_device,
550 SDL_WaylandPrimarySelectionSource *source,
551 const char **mime_types,
552 size_t mime_count)
553{
554 bool result = true;
555
556 if (!primary_selection_device) {
557 result = SDL_SetError("Invalid Primary Selection Device");
558 } else if (!source) {
559 result = SDL_SetError("Invalid source");
560 } else {
561 size_t index = 0;
562 const char *mime_type = mime_types[index];
563
564 for (index = 0; index < mime_count; ++index) {
565 mime_type = mime_types[index];
566 zwp_primary_selection_source_v1_offer(source->source, mime_type);
567 }
568
569 if (index == 0) {
570 Wayland_primary_selection_device_clear_selection(primary_selection_device);
571 result = SDL_SetError("No mime data");
572 } else {
573 // Only set if there is a valid serial if not set it later
574 if (primary_selection_device->selection_serial != 0) {
575 zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
576 source->source,
577 primary_selection_device->selection_serial);
578 }
579 if (primary_selection_device->selection_source) {
580 Wayland_primary_selection_source_destroy(primary_selection_device->selection_source);
581 }
582 primary_selection_device->selection_source = source;
583 source->primary_selection_device = primary_selection_device;
584 }
585 }
586
587 return result;
588}
589
590void Wayland_data_device_set_serial(SDL_WaylandDataDevice *data_device, uint32_t serial)
591{
592 if (data_device) {
593 // If there was no serial and there is a pending selection set it now.
594 if (data_device->selection_serial == 0 && data_device->selection_source) {
595 wl_data_device_set_selection(data_device->data_device,
596 data_device->selection_source->source,
597 data_device->selection_serial);
598 }
599
600 data_device->selection_serial = serial;
601 }
602}
603
604void Wayland_primary_selection_device_set_serial(SDL_WaylandPrimarySelectionDevice *primary_selection_device,
605 uint32_t serial)
606{
607 if (primary_selection_device) {
608 // If there was no serial and there is a pending selection set it now.
609 if (primary_selection_device->selection_serial == 0 && primary_selection_device->selection_source) {
610 zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
611 primary_selection_device->selection_source->source,
612 primary_selection_device->selection_serial);
613 }
614
615 primary_selection_device->selection_serial = serial;
616 }
617}
618
619#endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddatamanager.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddatamanager.h
new file mode 100644
index 0000000..adca17b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddatamanager.h
@@ -0,0 +1,162 @@
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#ifndef SDL_waylanddatamanager_h_
25#define SDL_waylanddatamanager_h_
26
27#include "SDL_waylandvideo.h"
28#include "SDL_waylandwindow.h"
29
30#define TEXT_MIME "text/plain;charset=utf-8"
31#define FILE_MIME "text/uri-list"
32#define FILE_PORTAL_MIME "application/vnd.portal.filetransfer"
33
34typedef struct
35{
36 char *mime_type;
37 void *data;
38 size_t length;
39 struct wl_list link;
40} SDL_MimeDataList;
41
42typedef struct SDL_WaylandUserdata
43{
44 Uint32 sequence;
45 void *data;
46} SDL_WaylandUserdata;
47
48typedef struct
49{
50 struct wl_data_source *source;
51 void *data_device;
52 SDL_ClipboardDataCallback callback;
53 SDL_WaylandUserdata userdata;
54} SDL_WaylandDataSource;
55
56typedef struct
57{
58 struct zwp_primary_selection_source_v1 *source;
59 void *data_device;
60 void *primary_selection_device;
61 SDL_ClipboardDataCallback callback;
62 SDL_WaylandUserdata userdata;
63} SDL_WaylandPrimarySelectionSource;
64
65typedef struct
66{
67 struct wl_data_offer *offer;
68 struct wl_list mimes;
69 void *data_device;
70} SDL_WaylandDataOffer;
71
72typedef struct
73{
74 struct zwp_primary_selection_offer_v1 *offer;
75 struct wl_list mimes;
76 void *primary_selection_device;
77} SDL_WaylandPrimarySelectionOffer;
78
79typedef struct
80{
81 struct wl_data_device *data_device;
82 SDL_VideoData *video_data;
83
84 // Drag and Drop
85 uint32_t drag_serial;
86 SDL_WaylandDataOffer *drag_offer;
87 SDL_WaylandDataOffer *selection_offer;
88 bool has_mime_file, has_mime_text;
89 SDL_Window *dnd_window;
90
91 // Clipboard and Primary Selection
92 uint32_t selection_serial;
93 SDL_WaylandDataSource *selection_source;
94} SDL_WaylandDataDevice;
95
96typedef struct
97{
98 struct zwp_primary_selection_device_v1 *primary_selection_device;
99 SDL_VideoData *video_data;
100
101 uint32_t selection_serial;
102 SDL_WaylandPrimarySelectionSource *selection_source;
103 SDL_WaylandPrimarySelectionOffer *selection_offer;
104} SDL_WaylandPrimarySelectionDevice;
105
106// Wayland Data Source / Primary Selection Source - (Sending)
107extern SDL_WaylandDataSource *Wayland_data_source_create(SDL_VideoDevice *_this);
108extern SDL_WaylandPrimarySelectionSource *Wayland_primary_selection_source_create(SDL_VideoDevice *_this);
109extern ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source,
110 const char *mime_type, int fd);
111extern ssize_t Wayland_primary_selection_source_send(SDL_WaylandPrimarySelectionSource *source,
112 const char *mime_type, int fd);
113extern void Wayland_data_source_set_callback(SDL_WaylandDataSource *source,
114 SDL_ClipboardDataCallback callback,
115 void *userdata,
116 Uint32 sequence);
117extern void Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSource *source,
118 SDL_ClipboardDataCallback callback,
119 void *userdata);
120extern void *Wayland_data_source_get_data(SDL_WaylandDataSource *source,
121 const char *mime_type,
122 size_t *length);
123extern void *Wayland_primary_selection_source_get_data(SDL_WaylandPrimarySelectionSource *source,
124 const char *mime_type,
125 size_t *length);
126extern void Wayland_data_source_destroy(SDL_WaylandDataSource *source);
127extern void Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource *source);
128
129// Wayland Data / Primary Selection Offer - (Receiving)
130extern void *Wayland_data_offer_receive(SDL_WaylandDataOffer *offer,
131 const char *mime_type,
132 size_t *length);
133extern void *Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelectionOffer *offer,
134 const char *mime_type,
135 size_t *length);
136extern bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer,
137 const char *mime_type);
138extern bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *offer,
139 const char *mime_type);
140extern bool Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer,
141 const char *mime_type);
142extern bool Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer *offer,
143 const char *mime_type);
144extern void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer);
145extern void Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionOffer *offer);
146
147// Clipboard / Primary Selection
148extern bool Wayland_data_device_clear_selection(SDL_WaylandDataDevice *device);
149extern bool Wayland_primary_selection_device_clear_selection(SDL_WaylandPrimarySelectionDevice *device);
150extern bool Wayland_data_device_set_selection(SDL_WaylandDataDevice *device,
151 SDL_WaylandDataSource *source,
152 const char **mime_types,
153 size_t mime_count);
154extern bool Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDevice *device,
155 SDL_WaylandPrimarySelectionSource *source,
156 const char **mime_types,
157 size_t mime_count);
158extern void Wayland_data_device_set_serial(SDL_WaylandDataDevice *device,
159 uint32_t serial);
160extern void Wayland_primary_selection_device_set_serial(SDL_WaylandPrimarySelectionDevice *device,
161 uint32_t serial);
162#endif // SDL_waylanddatamanager_h_
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddyn.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddyn.c
new file mode 100644
index 0000000..7d6d42f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddyn.c
@@ -0,0 +1,176 @@
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#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_WAYLAND
24
25#define DEBUG_DYNAMIC_WAYLAND 0
26
27#include "SDL_waylanddyn.h"
28
29#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC
30
31typedef struct
32{
33 SDL_SharedObject *lib;
34 const char *libname;
35} waylanddynlib;
36
37static waylanddynlib waylandlibs[] = {
38 { NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC },
39#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL
40 { NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_EGL },
41#endif
42#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR
43 { NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR },
44#endif
45#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON
46 { NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_XKBCOMMON },
47#endif
48#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR
49 { NULL, SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR },
50#endif
51 { NULL, NULL }
52};
53
54static void *WAYLAND_GetSym(const char *fnname, int *pHasModule, bool required)
55{
56 void *fn = NULL;
57 waylanddynlib *dynlib;
58 for (dynlib = waylandlibs; dynlib->libname; dynlib++) {
59 if (dynlib->lib) {
60 fn = SDL_LoadFunction(dynlib->lib, fnname);
61 if (fn) {
62 break;
63 }
64 }
65 }
66
67#if DEBUG_DYNAMIC_WAYLAND
68 if (fn) {
69 SDL_Log("WAYLAND: Found '%s' in %s (%p)", fnname, dynlib->libname, fn);
70 } else {
71 SDL_Log("WAYLAND: Symbol '%s' NOT FOUND!", fnname);
72 }
73#endif
74
75 if (!fn && required) {
76 *pHasModule = 0; // kill this module.
77 }
78
79 return fn;
80}
81
82#else
83
84#include <wayland-egl.h>
85
86#endif // SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC
87
88// Define all the function pointers and wrappers...
89#define SDL_WAYLAND_MODULE(modname) int SDL_WAYLAND_HAVE_##modname = 0;
90#define SDL_WAYLAND_SYM(rc, fn, params) SDL_DYNWAYLANDFN_##fn WAYLAND_##fn = NULL;
91#define SDL_WAYLAND_SYM_OPT(rc, fn, params) SDL_DYNWAYLANDFN_##fn WAYLAND_##fn = NULL;
92#define SDL_WAYLAND_INTERFACE(iface) const struct wl_interface *WAYLAND_##iface = NULL;
93#include "SDL_waylandsym.h"
94
95static int wayland_load_refcount = 0;
96
97void SDL_WAYLAND_UnloadSymbols(void)
98{
99 // Don't actually unload if more than one module is using the libs...
100 if (wayland_load_refcount > 0) {
101 if (--wayland_load_refcount == 0) {
102#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC
103 int i;
104#endif
105
106 // set all the function pointers to NULL.
107#define SDL_WAYLAND_MODULE(modname) SDL_WAYLAND_HAVE_##modname = 0;
108#define SDL_WAYLAND_SYM(rc, fn, params) WAYLAND_##fn = NULL;
109#define SDL_WAYLAND_SYM_OPT(rc, fn, params) WAYLAND_##fn = NULL;
110#define SDL_WAYLAND_INTERFACE(iface) WAYLAND_##iface = NULL;
111#include "SDL_waylandsym.h"
112
113#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC
114 for (i = 0; i < SDL_arraysize(waylandlibs); i++) {
115 if (waylandlibs[i].lib) {
116 SDL_UnloadObject(waylandlibs[i].lib);
117 waylandlibs[i].lib = NULL;
118 }
119 }
120#endif
121 }
122 }
123}
124
125// returns non-zero if all needed symbols were loaded.
126bool SDL_WAYLAND_LoadSymbols(void)
127{
128 bool result = true; // always succeed if not using Dynamic WAYLAND stuff.
129
130 // deal with multiple modules (dga, wayland, etc) needing these symbols...
131 if (wayland_load_refcount++ == 0) {
132#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC
133 int i;
134 int *thismod = NULL;
135 for (i = 0; i < SDL_arraysize(waylandlibs); i++) {
136 if (waylandlibs[i].libname) {
137 waylandlibs[i].lib = SDL_LoadObject(waylandlibs[i].libname);
138 }
139 }
140
141#define SDL_WAYLAND_MODULE(modname) SDL_WAYLAND_HAVE_##modname = 1; // default yes
142#include "SDL_waylandsym.h"
143
144#define SDL_WAYLAND_MODULE(modname) thismod = &SDL_WAYLAND_HAVE_##modname;
145#define SDL_WAYLAND_SYM(rc, fn, params) WAYLAND_##fn = (SDL_DYNWAYLANDFN_##fn)WAYLAND_GetSym(#fn, thismod, true);
146#define SDL_WAYLAND_SYM_OPT(rc, fn, params) WAYLAND_##fn = (SDL_DYNWAYLANDFN_##fn)WAYLAND_GetSym(#fn, thismod, false);
147#define SDL_WAYLAND_INTERFACE(iface) WAYLAND_##iface = (struct wl_interface *)WAYLAND_GetSym(#iface, thismod, true);
148#include "SDL_waylandsym.h"
149
150 if (SDL_WAYLAND_HAVE_WAYLAND_CLIENT &&
151 SDL_WAYLAND_HAVE_WAYLAND_CURSOR &&
152 SDL_WAYLAND_HAVE_WAYLAND_EGL &&
153 SDL_WAYLAND_HAVE_WAYLAND_XKB) {
154 // All required symbols loaded, only libdecor is optional.
155 SDL_ClearError();
156 } else {
157 // in case something got loaded...
158 SDL_WAYLAND_UnloadSymbols();
159 result = false;
160 }
161
162#else // no dynamic WAYLAND
163
164#define SDL_WAYLAND_MODULE(modname) SDL_WAYLAND_HAVE_##modname = 1; // default yes
165#define SDL_WAYLAND_SYM(rc, fn, params) WAYLAND_##fn = fn;
166#define SDL_WAYLAND_SYM_OPT(rc, fn, params) WAYLAND_##fn = fn;
167#define SDL_WAYLAND_INTERFACE(iface) WAYLAND_##iface = &iface;
168#include "SDL_waylandsym.h"
169
170#endif
171 }
172
173 return result;
174}
175
176#endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddyn.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddyn.h
new file mode 100644
index 0000000..fd85a7c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylanddyn.h
@@ -0,0 +1,195 @@
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#ifndef SDL_waylanddyn_h_
22#define SDL_waylanddyn_h_
23
24#include "SDL_internal.h"
25
26/* We can't include wayland-client.h here
27 * but we need some structs from it
28 */
29struct wl_interface;
30struct wl_proxy;
31struct wl_event_queue;
32struct wl_display;
33struct wl_surface;
34struct wl_shm;
35
36// We also need some for libdecor
37struct wl_seat;
38struct wl_output;
39struct libdecor;
40struct libdecor_frame;
41struct libdecor_state;
42struct libdecor_configuration;
43struct libdecor_interface;
44struct libdecor_frame_interface;
45enum libdecor_resize_edge;
46enum libdecor_capabilities;
47enum libdecor_window_state;
48
49#include "wayland-cursor.h"
50#include "wayland-util.h"
51#include "xkbcommon/xkbcommon.h"
52#include "xkbcommon/xkbcommon-compose.h"
53
54// Must be included before our #defines, see Bugzilla #4957
55#include "wayland-client-core.h"
56
57#define SDL_WAYLAND_CHECK_VERSION(x, y, z) \
58 (WAYLAND_VERSION_MAJOR > x || \
59 (WAYLAND_VERSION_MAJOR == x && WAYLAND_VERSION_MINOR > y) || \
60 (WAYLAND_VERSION_MAJOR == x && WAYLAND_VERSION_MINOR == y && WAYLAND_VERSION_MICRO >= z))
61
62#ifdef HAVE_LIBDECOR_H
63#define SDL_LIBDECOR_CHECK_VERSION(x, y, z) \
64 (SDL_LIBDECOR_VERSION_MAJOR > x || \
65 (SDL_LIBDECOR_VERSION_MAJOR == x && SDL_LIBDECOR_VERSION_MINOR > y) || \
66 (SDL_LIBDECOR_VERSION_MAJOR == x && SDL_LIBDECOR_VERSION_MINOR == y && SDL_LIBDECOR_VERSION_PATCH >= z))
67#endif
68
69#ifdef __cplusplus
70extern "C" {
71#endif
72
73extern bool SDL_WAYLAND_LoadSymbols(void);
74extern void SDL_WAYLAND_UnloadSymbols(void);
75
76#define SDL_WAYLAND_MODULE(modname) extern int SDL_WAYLAND_HAVE_##modname;
77#define SDL_WAYLAND_SYM(rc, fn, params) \
78 typedef rc(*SDL_DYNWAYLANDFN_##fn) params; \
79 extern SDL_DYNWAYLANDFN_##fn WAYLAND_##fn;
80#define SDL_WAYLAND_SYM_OPT(rc, fn, params) \
81 typedef rc(*SDL_DYNWAYLANDFN_##fn) params; \
82 extern SDL_DYNWAYLANDFN_##fn WAYLAND_##fn;
83#define SDL_WAYLAND_INTERFACE(iface) extern const struct wl_interface *WAYLAND_##iface;
84#include "SDL_waylandsym.h"
85
86#ifdef __cplusplus
87}
88#endif
89
90#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC
91
92#if defined(_WAYLAND_CLIENT_H) || defined(WAYLAND_CLIENT_H)
93#error Do not include wayland-client ahead of SDL_waylanddyn.h in dynamic loading mode
94#endif
95
96/* wayland-client-protocol.h included from wayland-client.h
97 * has inline functions that require these to be defined in dynamic loading mode
98 */
99
100#define wl_proxy_create (*WAYLAND_wl_proxy_create)
101#define wl_proxy_destroy (*WAYLAND_wl_proxy_destroy)
102#define wl_proxy_marshal (*WAYLAND_wl_proxy_marshal)
103#define wl_proxy_set_user_data (*WAYLAND_wl_proxy_set_user_data)
104#define wl_proxy_get_user_data (*WAYLAND_wl_proxy_get_user_data)
105#define wl_proxy_get_version (*WAYLAND_wl_proxy_get_version)
106#define wl_proxy_add_listener (*WAYLAND_wl_proxy_add_listener)
107#define wl_proxy_marshal_constructor (*WAYLAND_wl_proxy_marshal_constructor)
108#define wl_proxy_marshal_constructor_versioned (*WAYLAND_wl_proxy_marshal_constructor_versioned)
109#define wl_proxy_set_tag (*WAYLAND_wl_proxy_set_tag)
110#define wl_proxy_get_tag (*WAYLAND_wl_proxy_get_tag)
111#define wl_proxy_marshal_flags (*WAYLAND_wl_proxy_marshal_flags)
112#define wl_proxy_marshal_array_flags (*WAYLAND_wl_proxy_marshal_array_flags)
113#define wl_display_reconnect (*WAYLAND_wl_display_reconnect)
114
115#define wl_seat_interface (*WAYLAND_wl_seat_interface)
116#define wl_surface_interface (*WAYLAND_wl_surface_interface)
117#define wl_shm_pool_interface (*WAYLAND_wl_shm_pool_interface)
118#define wl_buffer_interface (*WAYLAND_wl_buffer_interface)
119#define wl_registry_interface (*WAYLAND_wl_registry_interface)
120#define wl_region_interface (*WAYLAND_wl_region_interface)
121#define wl_pointer_interface (*WAYLAND_wl_pointer_interface)
122#define wl_keyboard_interface (*WAYLAND_wl_keyboard_interface)
123#define wl_compositor_interface (*WAYLAND_wl_compositor_interface)
124#define wl_output_interface (*WAYLAND_wl_output_interface)
125#define wl_shm_interface (*WAYLAND_wl_shm_interface)
126#define wl_data_device_interface (*WAYLAND_wl_data_device_interface)
127#define wl_data_offer_interface (*WAYLAND_wl_data_offer_interface)
128#define wl_data_source_interface (*WAYLAND_wl_data_source_interface)
129#define wl_data_device_manager_interface (*WAYLAND_wl_data_device_manager_interface)
130
131/*
132 * These must be included before libdecor.h, otherwise the libdecor header
133 * pulls in the system Wayland protocol headers instead of ours.
134 */
135#include "wayland-client-protocol.h"
136#include "wayland-egl.h"
137
138#ifdef HAVE_LIBDECOR_H
139// Must be included before our defines
140#include <libdecor.h>
141
142#define libdecor_unref (*WAYLAND_libdecor_unref)
143#define libdecor_new (*WAYLAND_libdecor_new)
144#define libdecor_decorate (*WAYLAND_libdecor_decorate)
145#define libdecor_frame_unref (*WAYLAND_libdecor_frame_unref)
146#define libdecor_frame_set_title (*WAYLAND_libdecor_frame_set_title)
147#define libdecor_frame_set_app_id (*WAYLAND_libdecor_frame_set_app_id)
148#define libdecor_frame_set_max_content_size (*WAYLAND_libdecor_frame_set_max_content_size)
149#define libdecor_frame_get_max_content_size (*WAYLAND_libdecor_frame_get_max_content_size)
150#define libdecor_frame_set_min_content_size (*WAYLAND_libdecor_frame_set_min_content_size)
151#define libdecor_frame_get_min_content_size (*WAYLAND_libdecor_frame_get_min_content_size)
152#define libdecor_frame_resize (*WAYLAND_libdecor_frame_resize)
153#define libdecor_frame_move (*WAYLAND_libdecor_frame_move)
154#define libdecor_frame_commit (*WAYLAND_libdecor_frame_commit)
155#define libdecor_frame_set_minimized (*WAYLAND_libdecor_frame_set_minimized)
156#define libdecor_frame_set_maximized (*WAYLAND_libdecor_frame_set_maximized)
157#define libdecor_frame_unset_maximized (*WAYLAND_libdecor_frame_unset_maximized)
158#define libdecor_frame_set_fullscreen (*WAYLAND_libdecor_frame_set_fullscreen)
159#define libdecor_frame_unset_fullscreen (*WAYLAND_libdecor_frame_unset_fullscreen)
160#define libdecor_frame_set_capabilities (*WAYLAND_libdecor_frame_set_capabilities)
161#define libdecor_frame_unset_capabilities (*WAYLAND_libdecor_frame_unset_capabilities)
162#define libdecor_frame_has_capability (*WAYLAND_libdecor_frame_has_capability)
163#define libdecor_frame_set_visibility (*WAYLAND_libdecor_frame_set_visibility)
164#define libdecor_frame_is_visible (*WAYLAND_libdecor_frame_is_visible)
165#define libdecor_frame_is_floating (*WAYLAND_libdecor_frame_is_floating)
166#define libdecor_frame_set_parent (*WAYLAND_libdecor_frame_set_parent)
167#define libdecor_frame_show_window_menu (*WAYLAND_libdecor_frame_show_window_menu)
168#define libdecor_frame_get_wm_capabilities (*WAYLAND_libdecor_frame_get_wm_capabilities)
169#define libdecor_frame_get_xdg_surface (*WAYLAND_libdecor_frame_get_xdg_surface)
170#define libdecor_frame_get_xdg_toplevel (*WAYLAND_libdecor_frame_get_xdg_toplevel)
171#define libdecor_frame_translate_coordinate (*WAYLAND_libdecor_frame_translate_coordinate)
172#define libdecor_frame_map (*WAYLAND_libdecor_frame_map)
173#define libdecor_state_new (*WAYLAND_libdecor_state_new)
174#define libdecor_state_free (*WAYLAND_libdecor_state_free)
175#define libdecor_configuration_get_content_size (*WAYLAND_libdecor_configuration_get_content_size)
176#define libdecor_configuration_get_window_state (*WAYLAND_libdecor_configuration_get_window_state)
177#define libdecor_dispatch (*WAYLAND_libdecor_dispatch)
178#endif
179
180#else // SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC
181
182/*
183 * These must be included before libdecor.h, otherwise the libdecor header
184 * pulls in the system Wayland protocol headers instead of ours.
185 */
186#include "wayland-client-protocol.h"
187#include "wayland-egl.h"
188
189#ifdef HAVE_LIBDECOR_H
190#include <libdecor.h>
191#endif
192
193#endif // SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC
194
195#endif // SDL_waylanddyn_h_
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandevents.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandevents.c
new file mode 100644
index 0000000..83c414f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandevents.c
@@ -0,0 +1,3408 @@
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 "../../core/unix/SDL_poll.h"
27#include "../../events/SDL_events_c.h"
28#include "../../events/SDL_scancode_tables_c.h"
29#include "../../events/SDL_keysym_to_keycode_c.h"
30#include "../../core/linux/SDL_system_theme.h"
31#include "../SDL_sysvideo.h"
32
33#include "SDL_waylandvideo.h"
34#include "SDL_waylandevents_c.h"
35#include "SDL_waylandwindow.h"
36#include "SDL_waylandmouse.h"
37
38#include "pointer-constraints-unstable-v1-client-protocol.h"
39#include "relative-pointer-unstable-v1-client-protocol.h"
40#include "xdg-shell-client-protocol.h"
41#include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
42#include "text-input-unstable-v3-client-protocol.h"
43#include "tablet-v2-client-protocol.h"
44#include "primary-selection-unstable-v1-client-protocol.h"
45#include "input-timestamps-unstable-v1-client-protocol.h"
46
47#ifdef HAVE_LIBDECOR_H
48#include <libdecor.h>
49#endif
50
51#ifdef SDL_INPUT_LINUXEV
52#include <linux/input.h>
53#else
54#define BTN_LEFT (0x110)
55#define BTN_RIGHT (0x111)
56#define BTN_MIDDLE (0x112)
57#define BTN_SIDE (0x113)
58#define BTN_EXTRA (0x114)
59#endif
60#include "../../events/SDL_keysym_to_scancode_c.h"
61#include "../../events/imKStoUCS.h"
62#include <errno.h>
63#include <sys/mman.h>
64#include <unistd.h>
65#include <xkbcommon/xkbcommon-compose.h>
66#include <xkbcommon/xkbcommon.h>
67#include "cursor-shape-v1-client-protocol.h"
68
69// Weston uses a ratio of 10 units per scroll tick
70#define WAYLAND_WHEEL_AXIS_UNIT 10
71
72#ifndef XKB_MOD_NAME_MOD3
73#define XKB_MOD_NAME_MOD3 "Mod3"
74#endif
75
76#ifndef XKB_MOD_NAME_MOD5
77#define XKB_MOD_NAME_MOD5 "Mod5"
78#endif
79
80// Keyboard and mouse names to match XWayland
81#define WAYLAND_DEFAULT_KEYBOARD_NAME "Virtual core keyboard"
82#define WAYLAND_DEFAULT_POINTER_NAME "Virtual core pointer"
83
84// Focus clickthrough timeout
85#define WAYLAND_FOCUS_CLICK_TIMEOUT_NS SDL_MS_TO_NS(10)
86
87struct SDL_WaylandTouchPoint
88{
89 SDL_TouchID id;
90 wl_fixed_t fx;
91 wl_fixed_t fy;
92 struct wl_surface *surface;
93
94 struct wl_list link;
95};
96
97static struct wl_list touch_points;
98
99static void touch_add(SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface *surface)
100{
101 struct SDL_WaylandTouchPoint *tp = SDL_malloc(sizeof(struct SDL_WaylandTouchPoint));
102
103 SDL_zerop(tp);
104 tp->id = id;
105 tp->fx = fx;
106 tp->fy = fy;
107 tp->surface = surface;
108
109 WAYLAND_wl_list_insert(&touch_points, &tp->link);
110}
111
112static void touch_update(SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface **surface)
113{
114 struct SDL_WaylandTouchPoint *tp;
115
116 wl_list_for_each (tp, &touch_points, link) {
117 if (tp->id == id) {
118 tp->fx = fx;
119 tp->fy = fy;
120 if (surface) {
121 *surface = tp->surface;
122 }
123 break;
124 }
125 }
126}
127
128static void touch_del(SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_surface **surface)
129{
130 struct SDL_WaylandTouchPoint *tp;
131
132 wl_list_for_each (tp, &touch_points, link) {
133 if (tp->id == id) {
134 if (fx) {
135 *fx = tp->fx;
136 }
137 if (fy) {
138 *fy = tp->fy;
139 }
140 if (surface) {
141 *surface = tp->surface;
142 }
143
144 WAYLAND_wl_list_remove(&tp->link);
145 SDL_free(tp);
146 break;
147 }
148 }
149}
150
151static bool Wayland_SurfaceHasActiveTouches(struct wl_surface *surface)
152{
153 struct SDL_WaylandTouchPoint *tp;
154
155 wl_list_for_each (tp, &touch_points, link) {
156 if (tp->surface == surface) {
157 return true;
158 }
159 }
160
161 return false;
162}
163
164static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp)
165{
166 static Uint64 last;
167 static Uint64 timestamp_offset;
168 const Uint64 now = SDL_GetTicksNS();
169
170 if (nsTimestamp < last) {
171 // 32-bit timer rollover, bump the offset
172 timestamp_offset += SDL_MS_TO_NS(0x100000000LLU);
173 }
174 last = nsTimestamp;
175
176 if (!timestamp_offset) {
177 timestamp_offset = (now - nsTimestamp);
178 }
179 nsTimestamp += timestamp_offset;
180
181 if (nsTimestamp > now) {
182 timestamp_offset -= (nsTimestamp - now);
183 nsTimestamp = now;
184 }
185
186 return nsTimestamp;
187}
188
189static void Wayland_input_timestamp_listener(void *data, struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1,
190 uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec)
191{
192 *((Uint64 *)data) = ((((Uint64)tv_sec_hi << 32) | (Uint64)tv_sec_lo) * SDL_NS_PER_SECOND) + tv_nsec;
193}
194
195static const struct zwp_input_timestamps_v1_listener timestamp_listener = {
196 Wayland_input_timestamp_listener
197};
198
199static Uint64 Wayland_GetKeyboardTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
200{
201 if (wl_timestamp_ms) {
202 return Wayland_GetEventTimestamp(input->keyboard_timestamp_ns ? input->keyboard_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
203 }
204
205 return 0;
206}
207
208static Uint64 Wayland_GetKeyboardTimestampRaw(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
209{
210 if (wl_timestamp_ms) {
211 return input->keyboard_timestamp_ns ? input->keyboard_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms);
212 }
213
214 return 0;
215}
216
217static Uint64 Wayland_GetPointerTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
218{
219 if (wl_timestamp_ms) {
220 return Wayland_GetEventTimestamp(input->pointer_timestamp_ns ? input->pointer_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
221 }
222
223 return 0;
224}
225
226Uint64 Wayland_GetTouchTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
227{
228 if (wl_timestamp_ms) {
229 return Wayland_GetEventTimestamp(input->touch_timestamp_ns ? input->touch_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
230 }
231
232 return 0;
233}
234
235void Wayland_RegisterTimestampListeners(struct SDL_WaylandInput *input)
236{
237 SDL_VideoData *viddata = input->display;
238
239 if (viddata->input_timestamps_manager) {
240 if (input->keyboard && !input->keyboard_timestamps) {
241 input->keyboard_timestamps = zwp_input_timestamps_manager_v1_get_keyboard_timestamps(viddata->input_timestamps_manager, input->keyboard);
242 zwp_input_timestamps_v1_add_listener(input->keyboard_timestamps, &timestamp_listener, &input->keyboard_timestamp_ns);
243 }
244
245 if (input->pointer && !input->pointer_timestamps) {
246 input->pointer_timestamps = zwp_input_timestamps_manager_v1_get_pointer_timestamps(viddata->input_timestamps_manager, input->pointer);
247 zwp_input_timestamps_v1_add_listener(input->pointer_timestamps, &timestamp_listener, &input->pointer_timestamp_ns);
248 }
249
250 if (input->touch && !input->touch_timestamps) {
251 input->touch_timestamps = zwp_input_timestamps_manager_v1_get_touch_timestamps(viddata->input_timestamps_manager, input->touch);
252 zwp_input_timestamps_v1_add_listener(input->touch_timestamps, &timestamp_listener, &input->touch_timestamp_ns);
253 }
254 }
255}
256
257void Wayland_CreateCursorShapeDevice(struct SDL_WaylandInput *input)
258{
259 SDL_VideoData *viddata = input->display;
260
261 if (viddata->cursor_shape_manager) {
262 if (input->pointer && !input->cursor_shape) {
263 input->cursor_shape = wp_cursor_shape_manager_v1_get_pointer(viddata->cursor_shape_manager, input->pointer);
264 }
265 }
266}
267
268// Returns true if a key repeat event was due
269static bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, Uint64 elapsed)
270{
271 bool ret = false;
272 while (elapsed >= repeat_info->next_repeat_ns) {
273 if (repeat_info->scancode != SDL_SCANCODE_UNKNOWN) {
274 const Uint64 timestamp = repeat_info->wl_press_time_ns + repeat_info->next_repeat_ns;
275 SDL_SendKeyboardKeyIgnoreModifiers(Wayland_GetEventTimestamp(timestamp), repeat_info->keyboard_id, repeat_info->key, repeat_info->scancode, true);
276 }
277 if (repeat_info->text[0]) {
278 SDL_SendKeyboardText(repeat_info->text);
279 }
280 repeat_info->next_repeat_ns += SDL_NS_PER_SECOND / (Uint64)repeat_info->repeat_rate;
281 ret = true;
282 }
283 return ret;
284}
285
286static void keyboard_repeat_clear(SDL_WaylandKeyboardRepeat *repeat_info)
287{
288 if (!repeat_info->is_initialized) {
289 return;
290 }
291 repeat_info->is_key_down = false;
292}
293
294static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, Uint32 keyboard_id, uint32_t key, Uint64 wl_press_time_ns,
295 uint32_t scancode, bool has_text, char text[8])
296{
297 if (!repeat_info->is_initialized || !repeat_info->repeat_rate) {
298 return;
299 }
300 repeat_info->is_key_down = true;
301 repeat_info->keyboard_id = keyboard_id;
302 repeat_info->key = key;
303 repeat_info->wl_press_time_ns = wl_press_time_ns;
304 repeat_info->sdl_press_time_ns = SDL_GetTicksNS();
305 repeat_info->next_repeat_ns = SDL_MS_TO_NS(repeat_info->repeat_delay_ms);
306 repeat_info->scancode = scancode;
307 if (has_text) {
308 SDL_copyp(repeat_info->text, text);
309 } else {
310 repeat_info->text[0] = '\0';
311 }
312}
313
314static uint32_t keyboard_repeat_get_key(SDL_WaylandKeyboardRepeat *repeat_info)
315{
316 if (repeat_info->is_initialized && repeat_info->is_key_down) {
317 return repeat_info->key;
318 }
319
320 return 0;
321}
322
323static void keyboard_repeat_set_text(SDL_WaylandKeyboardRepeat *repeat_info, const char text[8])
324{
325 if (repeat_info->is_initialized) {
326 SDL_copyp(repeat_info->text, text);
327 }
328}
329
330static bool keyboard_repeat_is_set(SDL_WaylandKeyboardRepeat *repeat_info)
331{
332 return repeat_info->is_initialized && repeat_info->is_key_down;
333}
334
335static bool keyboard_repeat_key_is_set(SDL_WaylandKeyboardRepeat *repeat_info, uint32_t key)
336{
337 return repeat_info->is_initialized && repeat_info->is_key_down && key == repeat_info->key;
338}
339
340static void sync_done_handler(void *data, struct wl_callback *callback, uint32_t callback_data)
341{
342 // Nothing to do, just destroy the callback
343 wl_callback_destroy(callback);
344}
345
346static struct wl_callback_listener sync_listener = {
347 sync_done_handler
348};
349
350void Wayland_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window)
351{
352 SDL_VideoData *d = _this->internal;
353
354 /* Queue a sync event to unblock the event queue fd if it's empty and being waited on.
355 * TODO: Maybe use a pipe to avoid the compositor roundtrip?
356 */
357 struct wl_callback *cb = wl_display_sync(d->display);
358 wl_callback_add_listener(cb, &sync_listener, NULL);
359 WAYLAND_wl_display_flush(d->display);
360}
361
362static int dispatch_queued_events(SDL_VideoData *viddata)
363{
364 int rc;
365
366 /*
367 * NOTE: When reconnection is implemented, check if libdecor needs to be
368 * involved in the reconnection process.
369 */
370#ifdef HAVE_LIBDECOR_H
371 if (viddata->shell.libdecor) {
372 libdecor_dispatch(viddata->shell.libdecor, 0);
373 }
374#endif
375
376 rc = WAYLAND_wl_display_dispatch_pending(viddata->display);
377 return rc >= 0 ? 1 : rc;
378}
379
380int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
381{
382 SDL_VideoData *d = _this->internal;
383 struct SDL_WaylandInput *input = d->input;
384 bool key_repeat_active = false;
385
386 WAYLAND_wl_display_flush(d->display);
387
388#ifdef SDL_USE_IME
389 SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
390 if (!d->text_input_manager && keyboard_focus && SDL_TextInputActive(keyboard_focus)) {
391 SDL_IME_PumpEvents();
392 }
393#endif
394
395#ifdef SDL_USE_LIBDBUS
396 SDL_DBus_PumpEvents();
397#endif
398
399 // If key repeat is active, we'll need to cap our maximum wait time to handle repeats
400 if (input && keyboard_repeat_is_set(&input->keyboard_repeat)) {
401 const Uint64 elapsed = SDL_GetTicksNS() - input->keyboard_repeat.sdl_press_time_ns;
402 if (keyboard_repeat_handle(&input->keyboard_repeat, elapsed)) {
403 // A repeat key event was already due
404 return 1;
405 } else {
406 const Uint64 next_repeat_wait_time = (input->keyboard_repeat.next_repeat_ns - elapsed) + 1;
407 if (timeoutNS >= 0) {
408 timeoutNS = SDL_min(timeoutNS, next_repeat_wait_time);
409 } else {
410 timeoutNS = next_repeat_wait_time;
411 }
412 key_repeat_active = true;
413 }
414 }
415
416 /* wl_display_prepare_read() will return -1 if the default queue is not empty.
417 * If the default queue is empty, it will prepare us for our SDL_IOReady() call. */
418 if (WAYLAND_wl_display_prepare_read(d->display) == 0) {
419 // Use SDL_IOR_NO_RETRY to ensure SIGINT will break us out of our wait
420 int err = SDL_IOReady(WAYLAND_wl_display_get_fd(d->display), SDL_IOR_READ | SDL_IOR_NO_RETRY, timeoutNS);
421 if (err > 0) {
422 // There are new events available to read
423 WAYLAND_wl_display_read_events(d->display);
424 return dispatch_queued_events(d);
425 } else if (err == 0) {
426 // No events available within the timeout
427 WAYLAND_wl_display_cancel_read(d->display);
428
429 // If key repeat is active, we might have woken up to generate a key event
430 if (key_repeat_active) {
431 const Uint64 elapsed = SDL_GetTicksNS() - input->keyboard_repeat.sdl_press_time_ns;
432 if (keyboard_repeat_handle(&input->keyboard_repeat, elapsed)) {
433 return 1;
434 }
435 }
436
437 return 0;
438 } else {
439 // Error returned from poll()/select()
440 WAYLAND_wl_display_cancel_read(d->display);
441
442 if (errno == EINTR) {
443 /* If the wait was interrupted by a signal, we may have generated a
444 * SDL_EVENT_QUIT event. Let the caller know to call SDL_PumpEvents(). */
445 return 1;
446 } else {
447 return err;
448 }
449 }
450 } else {
451 // We already had pending events
452 return dispatch_queued_events(d);
453 }
454}
455
456void Wayland_PumpEvents(SDL_VideoDevice *_this)
457{
458 SDL_VideoData *d = _this->internal;
459 struct SDL_WaylandInput *input = d->input;
460 int err;
461
462#ifdef SDL_USE_IME
463 SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
464 if (!d->text_input_manager && keyboard_focus && SDL_TextInputActive(keyboard_focus)) {
465 SDL_IME_PumpEvents();
466 }
467#endif
468
469#ifdef SDL_USE_LIBDBUS
470 SDL_DBus_PumpEvents();
471#endif
472
473#ifdef HAVE_LIBDECOR_H
474 if (d->shell.libdecor) {
475 libdecor_dispatch(d->shell.libdecor, 0);
476 }
477#endif
478
479 WAYLAND_wl_display_flush(d->display);
480
481 /* wl_display_prepare_read() will return -1 if the default queue is not empty.
482 * If the default queue is empty, it will prepare us for our SDL_IOReady() call. */
483 if (WAYLAND_wl_display_prepare_read(d->display) == 0) {
484 if (SDL_IOReady(WAYLAND_wl_display_get_fd(d->display), SDL_IOR_READ, 0) > 0) {
485 WAYLAND_wl_display_read_events(d->display);
486 } else {
487 WAYLAND_wl_display_cancel_read(d->display);
488 }
489 }
490
491 // Dispatch any pre-existing pending events or new events we may have read
492 err = WAYLAND_wl_display_dispatch_pending(d->display);
493
494 if (input && keyboard_repeat_is_set(&input->keyboard_repeat)) {
495 const Uint64 elapsed = SDL_GetTicksNS() - input->keyboard_repeat.sdl_press_time_ns;
496 keyboard_repeat_handle(&input->keyboard_repeat, elapsed);
497 }
498
499 if (err < 0 && !d->display_disconnected) {
500 /* Something has failed with the Wayland connection -- for example,
501 * the compositor may have shut down and closed its end of the socket,
502 * or there is a library-specific error.
503 *
504 * Try to recover once, then quit.
505 */
506 if (!Wayland_VideoReconnect(_this)) {
507 d->display_disconnected = 1;
508 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Wayland display connection closed by server (fatal)");
509
510 /* Only send a single quit message, as application shutdown might call
511 * SDL_PumpEvents
512 */
513 SDL_SendQuit();
514 }
515 }
516}
517
518static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
519 uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w)
520{
521 struct SDL_WaylandInput *input = data;
522 SDL_WindowData *window_data = input->pointer_focus;
523 SDL_Window *window = window_data ? window_data->sdlwindow : NULL;
524
525 input->sx_w = sx_w;
526 input->sy_w = sy_w;
527 if (input->pointer_focus) {
528 const float sx = (float)(wl_fixed_to_double(sx_w) * window_data->pointer_scale.x);
529 const float sy = (float)(wl_fixed_to_double(sy_w) * window_data->pointer_scale.y);
530 SDL_SendMouseMotion(Wayland_GetPointerTimestamp(input, time), window_data->sdlwindow, input->pointer_id, false, sx, sy);
531 }
532
533 if (window && window->hit_test) {
534 const SDL_Point point = { (int)SDL_floor(wl_fixed_to_double(sx_w) * window_data->pointer_scale.x),
535 (int)SDL_floor(wl_fixed_to_double(sy_w) * window_data->pointer_scale.y) };
536 SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
537 if (rc == window_data->hit_test_result) {
538 return;
539 }
540
541 Wayland_SetHitTestCursor(rc);
542 window_data->hit_test_result = rc;
543 }
544}
545
546static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
547 uint32_t serial, struct wl_surface *surface,
548 wl_fixed_t sx_w, wl_fixed_t sy_w)
549{
550 struct SDL_WaylandInput *input = data;
551 SDL_WindowData *window;
552
553 if (!surface) {
554 // enter event for a window we've just destroyed
555 return;
556 }
557
558 /* This handler will be called twice in Wayland 1.4
559 * Once for the window surface which has valid user data
560 * and again for the mouse cursor surface which does not have valid user data
561 * We ignore the later
562 */
563 window = Wayland_GetWindowDataForOwnedSurface(surface);
564
565 if (window) {
566 input->pointer_focus = window;
567 input->pointer_enter_serial = serial;
568 SDL_SetMouseFocus(window->sdlwindow);
569 /* In the case of e.g. a pointer confine warp, we may receive an enter
570 * event with no following motion event, but with the new coordinates
571 * as part of the enter event.
572 *
573 * FIXME: This causes a movement event with an anomalous timestamp when
574 * the cursor enters the window.
575 */
576 pointer_handle_motion(data, pointer, 0, sx_w, sy_w);
577 /* If the cursor was changed while our window didn't have pointer
578 * focus, we might need to trigger another call to
579 * wl_pointer_set_cursor() for the new cursor to be displayed. */
580 Wayland_SetHitTestCursor(window->hit_test_result);
581 }
582}
583
584static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
585 uint32_t serial, struct wl_surface *surface)
586{
587 struct SDL_WaylandInput *input = data;
588
589 if (!surface) {
590 return;
591 }
592
593 if (input->pointer_focus) {
594 SDL_WindowData *wind = Wayland_GetWindowDataForOwnedSurface(surface);
595
596 if (wind) {
597 // Clear the capture flag and raise all buttons
598 wind->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
599
600 input->buttons_pressed = 0;
601 SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_LEFT, false);
602 SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_RIGHT, false);
603 SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_MIDDLE, false);
604 SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_X1, false);
605 SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_X2, false);
606 }
607
608 /* A pointer leave event may be emitted if the compositor hides the pointer in response to receiving a touch event.
609 * Don't relinquish focus if the surface has active touches, as the compositor is just transitioning from mouse to touch mode.
610 */
611 if (!Wayland_SurfaceHasActiveTouches(surface)) {
612 SDL_SetMouseFocus(NULL);
613 }
614 input->pointer_focus = NULL;
615 }
616}
617
618static bool ProcessHitTest(SDL_WindowData *window_data,
619 struct wl_seat *seat,
620 wl_fixed_t sx_w, wl_fixed_t sy_w,
621 uint32_t serial)
622{
623 SDL_Window *window = window_data->sdlwindow;
624
625 if (window->hit_test) {
626 static const uint32_t directions[] = {
627 XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT, XDG_TOPLEVEL_RESIZE_EDGE_TOP,
628 XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT, XDG_TOPLEVEL_RESIZE_EDGE_RIGHT,
629 XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM,
630 XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT, XDG_TOPLEVEL_RESIZE_EDGE_LEFT
631 };
632
633#ifdef HAVE_LIBDECOR_H
634 static const uint32_t directions_libdecor[] = {
635 LIBDECOR_RESIZE_EDGE_TOP_LEFT, LIBDECOR_RESIZE_EDGE_TOP,
636 LIBDECOR_RESIZE_EDGE_TOP_RIGHT, LIBDECOR_RESIZE_EDGE_RIGHT,
637 LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT, LIBDECOR_RESIZE_EDGE_BOTTOM,
638 LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT, LIBDECOR_RESIZE_EDGE_LEFT
639 };
640#endif
641
642 switch (window_data->hit_test_result) {
643 case SDL_HITTEST_DRAGGABLE:
644#ifdef HAVE_LIBDECOR_H
645 if (window_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
646 if (window_data->shell_surface.libdecor.frame) {
647 libdecor_frame_move(window_data->shell_surface.libdecor.frame,
648 seat,
649 serial);
650 }
651 } else
652#endif
653 if (window_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
654 if (window_data->shell_surface.xdg.toplevel.xdg_toplevel) {
655 xdg_toplevel_move(window_data->shell_surface.xdg.toplevel.xdg_toplevel,
656 seat,
657 serial);
658 }
659 }
660 return true;
661
662 case SDL_HITTEST_RESIZE_TOPLEFT:
663 case SDL_HITTEST_RESIZE_TOP:
664 case SDL_HITTEST_RESIZE_TOPRIGHT:
665 case SDL_HITTEST_RESIZE_RIGHT:
666 case SDL_HITTEST_RESIZE_BOTTOMRIGHT:
667 case SDL_HITTEST_RESIZE_BOTTOM:
668 case SDL_HITTEST_RESIZE_BOTTOMLEFT:
669 case SDL_HITTEST_RESIZE_LEFT:
670#ifdef HAVE_LIBDECOR_H
671 if (window_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
672 if (window_data->shell_surface.libdecor.frame) {
673 libdecor_frame_resize(window_data->shell_surface.libdecor.frame,
674 seat,
675 serial,
676 directions_libdecor[window_data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]);
677 }
678 } else
679#endif
680 if (window_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
681 if (window_data->shell_surface.xdg.toplevel.xdg_toplevel) {
682 xdg_toplevel_resize(window_data->shell_surface.xdg.toplevel.xdg_toplevel,
683 seat,
684 serial,
685 directions[window_data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]);
686 }
687 }
688 return true;
689
690 default:
691 return false;
692 }
693 }
694
695 return false;
696}
697
698static void pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_t serial,
699 uint32_t time, uint32_t button, uint32_t state_w)
700{
701 SDL_WindowData *window = input->pointer_focus;
702 enum wl_pointer_button_state state = state_w;
703 Uint64 timestamp = Wayland_GetPointerTimestamp(input, time);
704 Uint8 sdl_button;
705 const bool down = (state != 0);
706
707 switch (button) {
708 case BTN_LEFT:
709 sdl_button = SDL_BUTTON_LEFT;
710 break;
711 case BTN_MIDDLE:
712 sdl_button = SDL_BUTTON_MIDDLE;
713 break;
714 case BTN_RIGHT:
715 sdl_button = SDL_BUTTON_RIGHT;
716 break;
717 case BTN_SIDE:
718 sdl_button = SDL_BUTTON_X1;
719 break;
720 case BTN_EXTRA:
721 sdl_button = SDL_BUTTON_X2;
722 break;
723 default:
724 return;
725 }
726
727 if (window) {
728 SDL_VideoData *viddata = window->waylandData;
729 bool ignore_click = false;
730
731 if (state) {
732 Wayland_UpdateImplicitGrabSerial(input, serial);
733 input->buttons_pressed |= SDL_BUTTON_MASK(sdl_button);
734 } else {
735 input->buttons_pressed &= ~(SDL_BUTTON_MASK(sdl_button));
736 }
737
738 if (sdl_button == SDL_BUTTON_LEFT &&
739 ProcessHitTest(input->pointer_focus, input->seat, input->sx_w, input->sy_w, serial)) {
740 return; // don't pass this event on to app.
741 }
742
743 // Possibly ignore this click if it was to gain focus.
744 if (window->last_focus_event_time_ns) {
745 if (state == WL_POINTER_BUTTON_STATE_PRESSED &&
746 (SDL_GetTicksNS() - window->last_focus_event_time_ns) < WAYLAND_FOCUS_CLICK_TIMEOUT_NS) {
747 ignore_click = !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false);
748 }
749
750 window->last_focus_event_time_ns = 0;
751 }
752
753 /* Wayland won't let you "capture" the mouse, but it will automatically track
754 * the mouse outside the window if you drag outside of it, until you let go
755 * of all buttons (even if you add or remove presses outside the window, as
756 * long as any button is still down, the capture remains).
757 *
758 * The mouse is not captured in relative mode.
759 */
760 if (!viddata->relative_mouse_mode) {
761 if (input->buttons_pressed != 0) {
762 window->sdlwindow->flags |= SDL_WINDOW_MOUSE_CAPTURE;
763 } else {
764 window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
765 }
766 }
767
768 if (!ignore_click) {
769 SDL_SendMouseButton(timestamp, window->sdlwindow, input->pointer_id, sdl_button, down);
770 }
771 }
772}
773
774static void pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial,
775 uint32_t time, uint32_t button, uint32_t state_w)
776{
777 struct SDL_WaylandInput *input = data;
778
779 pointer_handle_button_common(input, serial, time, button, state_w);
780}
781
782static void pointer_handle_axis_common_v1(struct SDL_WaylandInput *input,
783 uint32_t time, uint32_t axis, wl_fixed_t value)
784{
785 SDL_WindowData *window = input->pointer_focus;
786 const Uint64 timestamp = Wayland_GetPointerTimestamp(input, time);
787 const enum wl_pointer_axis a = axis;
788
789 if (input->pointer_focus) {
790 float x, y;
791
792 switch (a) {
793 case WL_POINTER_AXIS_VERTICAL_SCROLL:
794 x = 0;
795 y = 0 - (float)wl_fixed_to_double(value);
796 break;
797 case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
798 x = (float)wl_fixed_to_double(value);
799 y = 0;
800 break;
801 default:
802 return;
803 }
804
805 x /= WAYLAND_WHEEL_AXIS_UNIT;
806 y /= WAYLAND_WHEEL_AXIS_UNIT;
807
808 SDL_SendMouseWheel(timestamp, window->sdlwindow, input->pointer_id, x, y, SDL_MOUSEWHEEL_NORMAL);
809 }
810}
811
812static void pointer_handle_axis_common(struct SDL_WaylandInput *input, enum SDL_WaylandAxisEvent type,
813 uint32_t axis, wl_fixed_t value)
814{
815 const enum wl_pointer_axis a = axis;
816
817 if (input->pointer_focus) {
818 switch (a) {
819 case WL_POINTER_AXIS_VERTICAL_SCROLL:
820 switch (type) {
821 case AXIS_EVENT_VALUE120:
822 /*
823 * High resolution scroll event. The spec doesn't state that axis_value120
824 * events are limited to one per frame, so the values are accumulated.
825 */
826 if (input->pointer_curr_axis_info.y_axis_type != AXIS_EVENT_VALUE120) {
827 input->pointer_curr_axis_info.y_axis_type = AXIS_EVENT_VALUE120;
828 input->pointer_curr_axis_info.y = 0.0f;
829 }
830 input->pointer_curr_axis_info.y += 0 - (float)wl_fixed_to_double(value);
831 break;
832 case AXIS_EVENT_DISCRETE:
833 /*
834 * This is a discrete axis event, so we process it and set the
835 * flag to ignore future continuous axis events in this frame.
836 */
837 if (input->pointer_curr_axis_info.y_axis_type != AXIS_EVENT_DISCRETE) {
838 input->pointer_curr_axis_info.y_axis_type = AXIS_EVENT_DISCRETE;
839 input->pointer_curr_axis_info.y = 0 - (float)wl_fixed_to_double(value);
840 }
841 break;
842 case AXIS_EVENT_CONTINUOUS:
843 // Only process continuous events if no discrete events have been received.
844 if (input->pointer_curr_axis_info.y_axis_type == AXIS_EVENT_CONTINUOUS) {
845 input->pointer_curr_axis_info.y = 0 - (float)wl_fixed_to_double(value);
846 }
847 break;
848 }
849 break;
850 case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
851 switch (type) {
852 case AXIS_EVENT_VALUE120:
853 /*
854 * High resolution scroll event. The spec doesn't state that axis_value120
855 * events are limited to one per frame, so the values are accumulated.
856 */
857 if (input->pointer_curr_axis_info.x_axis_type != AXIS_EVENT_VALUE120) {
858 input->pointer_curr_axis_info.x_axis_type = AXIS_EVENT_VALUE120;
859 input->pointer_curr_axis_info.x = 0.0f;
860 }
861 input->pointer_curr_axis_info.x += (float)wl_fixed_to_double(value);
862 break;
863 case AXIS_EVENT_DISCRETE:
864 /*
865 * This is a discrete axis event, so we process it and set the
866 * flag to ignore future continuous axis events in this frame.
867 */
868 if (input->pointer_curr_axis_info.x_axis_type != AXIS_EVENT_DISCRETE) {
869 input->pointer_curr_axis_info.x_axis_type = AXIS_EVENT_DISCRETE;
870 input->pointer_curr_axis_info.x = (float)wl_fixed_to_double(value);
871 }
872 break;
873 case AXIS_EVENT_CONTINUOUS:
874 // Only process continuous events if no discrete events have been received.
875 if (input->pointer_curr_axis_info.x_axis_type == AXIS_EVENT_CONTINUOUS) {
876 input->pointer_curr_axis_info.x = (float)wl_fixed_to_double(value);
877 }
878 break;
879 }
880 break;
881 }
882 }
883}
884
885static void pointer_handle_axis(void *data, struct wl_pointer *pointer,
886 uint32_t time, uint32_t axis, wl_fixed_t value)
887{
888 struct SDL_WaylandInput *input = data;
889
890 if (wl_seat_get_version(input->seat) >= WL_POINTER_FRAME_SINCE_VERSION) {
891 input->pointer_curr_axis_info.timestamp_ns = Wayland_GetPointerTimestamp(input, time);
892 pointer_handle_axis_common(input, AXIS_EVENT_CONTINUOUS, axis, value);
893 } else {
894 pointer_handle_axis_common_v1(input, time, axis, value);
895 }
896}
897
898static void pointer_handle_axis_relative_direction(void *data, struct wl_pointer *pointer,
899 uint32_t axis, uint32_t axis_relative_direction)
900{
901 struct SDL_WaylandInput *input = data;
902 if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) {
903 return;
904 }
905 switch (axis_relative_direction) {
906 case WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL:
907 input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_NORMAL;
908 break;
909 case WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED:
910 input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_FLIPPED;
911 break;
912 }
913}
914
915static void pointer_handle_frame(void *data, struct wl_pointer *pointer)
916{
917 struct SDL_WaylandInput *input = data;
918 SDL_WindowData *window = input->pointer_focus;
919 float x, y;
920 SDL_MouseWheelDirection direction = input->pointer_curr_axis_info.direction;
921
922 switch (input->pointer_curr_axis_info.x_axis_type) {
923 case AXIS_EVENT_CONTINUOUS:
924 x = input->pointer_curr_axis_info.x / WAYLAND_WHEEL_AXIS_UNIT;
925 break;
926 case AXIS_EVENT_DISCRETE:
927 x = input->pointer_curr_axis_info.x;
928 break;
929 case AXIS_EVENT_VALUE120:
930 x = input->pointer_curr_axis_info.x / 120.0f;
931 break;
932 default:
933 x = 0.0f;
934 break;
935 }
936
937 switch (input->pointer_curr_axis_info.y_axis_type) {
938 case AXIS_EVENT_CONTINUOUS:
939 y = input->pointer_curr_axis_info.y / WAYLAND_WHEEL_AXIS_UNIT;
940 break;
941 case AXIS_EVENT_DISCRETE:
942 y = input->pointer_curr_axis_info.y;
943 break;
944 case AXIS_EVENT_VALUE120:
945 y = input->pointer_curr_axis_info.y / 120.0f;
946 break;
947 default:
948 y = 0.0f;
949 break;
950 }
951
952 // clear pointer_curr_axis_info for next frame
953 SDL_memset(&input->pointer_curr_axis_info, 0, sizeof(input->pointer_curr_axis_info));
954
955 if (x != 0.0f || y != 0.0f) {
956 SDL_SendMouseWheel(input->pointer_curr_axis_info.timestamp_ns,
957 window->sdlwindow, input->pointer_id, x, y, direction);
958 }
959}
960
961static void pointer_handle_axis_source(void *data, struct wl_pointer *pointer,
962 uint32_t axis_source)
963{
964 // unimplemented
965}
966
967static void pointer_handle_axis_stop(void *data, struct wl_pointer *pointer,
968 uint32_t time, uint32_t axis)
969{
970 // unimplemented
971}
972
973static void pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer,
974 uint32_t axis, int32_t discrete)
975{
976 struct SDL_WaylandInput *input = data;
977
978 pointer_handle_axis_common(input, AXIS_EVENT_DISCRETE, axis, wl_fixed_from_int(discrete));
979}
980
981static void pointer_handle_axis_value120(void *data, struct wl_pointer *pointer,
982 uint32_t axis, int32_t value120)
983{
984 struct SDL_WaylandInput *input = data;
985
986 pointer_handle_axis_common(input, AXIS_EVENT_VALUE120, axis, wl_fixed_from_int(value120));
987}
988
989static const struct wl_pointer_listener pointer_listener = {
990 pointer_handle_enter,
991 pointer_handle_leave,
992 pointer_handle_motion,
993 pointer_handle_button,
994 pointer_handle_axis,
995 pointer_handle_frame, // Version 5
996 pointer_handle_axis_source, // Version 5
997 pointer_handle_axis_stop, // Version 5
998 pointer_handle_axis_discrete, // Version 5
999 pointer_handle_axis_value120, // Version 8
1000 pointer_handle_axis_relative_direction // Version 9
1001};
1002
1003static void relative_pointer_handle_relative_motion(void *data,
1004 struct zwp_relative_pointer_v1 *pointer,
1005 uint32_t time_hi,
1006 uint32_t time_lo,
1007 wl_fixed_t dx_w,
1008 wl_fixed_t dy_w,
1009 wl_fixed_t dx_unaccel_w,
1010 wl_fixed_t dy_unaccel_w)
1011{
1012 struct SDL_WaylandInput *input = data;
1013 SDL_VideoData *d = input->display;
1014 SDL_WindowData *window = input->pointer_focus;
1015
1016 // Relative pointer event times are in microsecond granularity.
1017 const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo));
1018
1019 if (input->pointer_focus && d->relative_mouse_mode) {
1020 double dx;
1021 double dy;
1022 if (!SDL_GetMouse()->enable_relative_system_scale) {
1023 dx = wl_fixed_to_double(dx_unaccel_w);
1024 dy = wl_fixed_to_double(dy_unaccel_w);
1025 } else {
1026 dx = wl_fixed_to_double(dx_w);
1027 dy = wl_fixed_to_double(dy_w);
1028 }
1029
1030 SDL_SendMouseMotion(timestamp, window->sdlwindow, input->pointer_id, true, (float)dx, (float)dy);
1031 }
1032}
1033
1034static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = {
1035 relative_pointer_handle_relative_motion,
1036};
1037
1038static void locked_pointer_locked(void *data,
1039 struct zwp_locked_pointer_v1 *locked_pointer)
1040{
1041}
1042
1043static void locked_pointer_unlocked(void *data,
1044 struct zwp_locked_pointer_v1 *locked_pointer)
1045{
1046}
1047
1048static const struct zwp_locked_pointer_v1_listener locked_pointer_listener = {
1049 locked_pointer_locked,
1050 locked_pointer_unlocked,
1051};
1052
1053bool Wayland_input_lock_pointer(struct SDL_WaylandInput *input, SDL_Window *window)
1054{
1055 SDL_WindowData *w = window->internal;
1056 SDL_VideoData *d = input->display;
1057
1058 if (!d->pointer_constraints || !input->pointer) {
1059 return false;
1060 }
1061
1062 if (!w->locked_pointer) {
1063 if (w->confined_pointer) {
1064 // If the pointer is already confined to the surface, the lock will fail with a protocol error.
1065 Wayland_input_unconfine_pointer(input, window);
1066 }
1067
1068 w->locked_pointer = zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints,
1069 w->surface,
1070 input->pointer,
1071 NULL,
1072 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
1073 zwp_locked_pointer_v1_add_listener(w->locked_pointer,
1074 &locked_pointer_listener,
1075 window);
1076 }
1077
1078 return true;
1079}
1080
1081bool Wayland_input_unlock_pointer(struct SDL_WaylandInput *input, SDL_Window *window)
1082{
1083 SDL_WindowData *w = window->internal;
1084
1085 if (w->locked_pointer) {
1086 zwp_locked_pointer_v1_destroy(w->locked_pointer);
1087 w->locked_pointer = NULL;
1088 }
1089
1090 // Restore existing pointer confinement.
1091 Wayland_input_confine_pointer(input, window);
1092
1093 return true;
1094}
1095
1096static void pointer_confine_destroy(SDL_Window *window)
1097{
1098 SDL_WindowData *w = window->internal;
1099 if (w->confined_pointer) {
1100 zwp_confined_pointer_v1_destroy(w->confined_pointer);
1101 w->confined_pointer = NULL;
1102 }
1103}
1104
1105static void confined_pointer_confined(void *data,
1106 struct zwp_confined_pointer_v1 *confined_pointer)
1107{
1108}
1109
1110static void confined_pointer_unconfined(void *data,
1111 struct zwp_confined_pointer_v1 *confined_pointer)
1112{
1113}
1114
1115static const struct zwp_confined_pointer_v1_listener confined_pointer_listener = {
1116 confined_pointer_confined,
1117 confined_pointer_unconfined,
1118};
1119
1120static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t serial,
1121 uint32_t timestamp, struct wl_surface *surface,
1122 int id, wl_fixed_t fx, wl_fixed_t fy)
1123{
1124 struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
1125 SDL_WindowData *window_data;
1126
1127 // Check that this surface is valid.
1128 if (!surface) {
1129 return;
1130 }
1131
1132 touch_add(id, fx, fy, surface);
1133 Wayland_UpdateImplicitGrabSerial(input, serial);
1134 window_data = Wayland_GetWindowDataForOwnedSurface(surface);
1135
1136 if (window_data) {
1137 float x, y;
1138
1139 if (window_data->current.logical_width <= 1) {
1140 x = 0.5f;
1141 } else {
1142 x = (float)wl_fixed_to_double(fx) / (window_data->current.logical_width - 1);
1143 }
1144 if (window_data->current.logical_height <= 1) {
1145 y = 0.5f;
1146 } else {
1147 y = (float)wl_fixed_to_double(fy) / (window_data->current.logical_height - 1);
1148 }
1149
1150 SDL_SetMouseFocus(window_data->sdlwindow);
1151
1152 SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(uintptr_t)touch,
1153 (SDL_FingerID)(id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_DOWN, x, y, 1.0f);
1154 }
1155}
1156
1157static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial,
1158 uint32_t timestamp, int id)
1159{
1160 struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
1161 wl_fixed_t fx = 0, fy = 0;
1162 struct wl_surface *surface = NULL;
1163
1164 touch_del(id, &fx, &fy, &surface);
1165
1166 if (surface) {
1167 SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
1168
1169 if (window_data) {
1170 const float x = (float)wl_fixed_to_double(fx) / window_data->current.logical_width;
1171 const float y = (float)wl_fixed_to_double(fy) / window_data->current.logical_height;
1172
1173 SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(uintptr_t)touch,
1174 (SDL_FingerID)(id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_UP, x, y, 0.0f);
1175
1176 /* If the seat lacks pointer focus, the seat's keyboard focus is another window or NULL, this window currently
1177 * has mouse focus, and the surface has no active touch events, consider mouse focus to be lost.
1178 */
1179 if (!input->pointer_focus && input->keyboard_focus != window_data &&
1180 SDL_GetMouseFocus() == window_data->sdlwindow && !Wayland_SurfaceHasActiveTouches(surface)) {
1181 SDL_SetMouseFocus(NULL);
1182 }
1183 }
1184 }
1185}
1186
1187static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t timestamp,
1188 int id, wl_fixed_t fx, wl_fixed_t fy)
1189{
1190 struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
1191 struct wl_surface *surface = NULL;
1192
1193 touch_update(id, fx, fy, &surface);
1194
1195 if (surface) {
1196 SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
1197
1198 if (window_data) {
1199 const float x = (float)wl_fixed_to_double(fx) / window_data->current.logical_width;
1200 const float y = (float)wl_fixed_to_double(fy) / window_data->current.logical_height;
1201
1202 SDL_SendTouchMotion(Wayland_GetPointerTimestamp(input, timestamp), (SDL_TouchID)(uintptr_t)touch,
1203 (SDL_FingerID)(id + 1), window_data->sdlwindow, x, y, 1.0f);
1204 }
1205 }
1206}
1207
1208static void touch_handler_frame(void *data, struct wl_touch *touch)
1209{
1210}
1211
1212static void touch_handler_cancel(void *data, struct wl_touch *touch)
1213{
1214 struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
1215 struct SDL_WaylandTouchPoint *tp, *temp;
1216
1217 wl_list_for_each_safe (tp, temp, &touch_points, link) {
1218 bool removed = false;
1219
1220 if (tp->surface) {
1221 SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(tp->surface);
1222
1223 if (window_data) {
1224 const float x = (float)(wl_fixed_to_double(tp->fx) / window_data->current.logical_width);
1225 const float y = (float)(wl_fixed_to_double(tp->fy) / window_data->current.logical_height);
1226
1227 SDL_SendTouch(0, (SDL_TouchID)(uintptr_t)touch,
1228 (SDL_FingerID)(tp->id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_CANCELED, x, y, 0.0f);
1229
1230 // Remove the touch from the list before checking for still-active touches on the surface.
1231 WAYLAND_wl_list_remove(&tp->link);
1232 removed = true;
1233
1234 /* If the seat lacks pointer focus, the seat's keyboard focus is another window or NULL, this window currently
1235 * has mouse focus, and the surface has no active touch events, consider mouse focus to be lost.
1236 */
1237 if (!input->pointer_focus && input->keyboard_focus != window_data &&
1238 SDL_GetMouseFocus() == window_data->sdlwindow && !Wayland_SurfaceHasActiveTouches(tp->surface)) {
1239 SDL_SetMouseFocus(NULL);
1240 }
1241 }
1242 }
1243
1244 if (!removed) {
1245 WAYLAND_wl_list_remove(&tp->link);
1246 }
1247 SDL_free(tp);
1248 }
1249}
1250
1251static const struct wl_touch_listener touch_listener = {
1252 touch_handler_down,
1253 touch_handler_up,
1254 touch_handler_motion,
1255 touch_handler_frame,
1256 touch_handler_cancel,
1257 NULL, // shape
1258 NULL, // orientation
1259};
1260
1261typedef struct Wayland_Keymap
1262{
1263 SDL_Keymap *keymap;
1264 struct xkb_state *state;
1265 SDL_Keymod modstate;
1266} Wayland_Keymap;
1267
1268static void Wayland_keymap_iter(struct xkb_keymap *keymap, xkb_keycode_t key, void *data)
1269{
1270 Wayland_Keymap *sdlKeymap = (Wayland_Keymap *)data;
1271 const xkb_keysym_t *syms;
1272 const SDL_Scancode scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, (key - 8));
1273 if (scancode == SDL_SCANCODE_UNKNOWN) {
1274 return;
1275 }
1276
1277 if (WAYLAND_xkb_state_key_get_syms(sdlKeymap->state, key, &syms) > 0) {
1278 SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(syms[0], key, sdlKeymap->modstate);
1279
1280 if (!keycode) {
1281 switch (scancode) {
1282 case SDL_SCANCODE_RETURN:
1283 keycode = SDLK_RETURN;
1284 break;
1285 case SDL_SCANCODE_ESCAPE:
1286 keycode = SDLK_ESCAPE;
1287 break;
1288 case SDL_SCANCODE_BACKSPACE:
1289 keycode = SDLK_BACKSPACE;
1290 break;
1291 case SDL_SCANCODE_DELETE:
1292 keycode = SDLK_DELETE;
1293 break;
1294 default:
1295 keycode = SDL_SCANCODE_TO_KEYCODE(scancode);
1296 break;
1297 }
1298 }
1299
1300 SDL_SetKeymapEntry(sdlKeymap->keymap, scancode, sdlKeymap->modstate, keycode);
1301 }
1302}
1303
1304static void Wayland_UpdateKeymap(struct SDL_WaylandInput *input)
1305{
1306 struct Keymod_masks
1307 {
1308 SDL_Keymod sdl_mask;
1309 xkb_mod_mask_t xkb_mask;
1310 } const keymod_masks[] = {
1311 { SDL_KMOD_NONE, 0 },
1312 { SDL_KMOD_SHIFT, input->xkb.idx_shift },
1313 { SDL_KMOD_CAPS, input->xkb.idx_caps },
1314 { SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_shift | input->xkb.idx_caps },
1315 { SDL_KMOD_MODE, input->xkb.idx_mod5 },
1316 { SDL_KMOD_MODE | SDL_KMOD_SHIFT, input->xkb.idx_mod5 | input->xkb.idx_shift },
1317 { SDL_KMOD_MODE | SDL_KMOD_CAPS, input->xkb.idx_mod5 | input->xkb.idx_caps },
1318 { SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_mod5 | input->xkb.idx_shift | input->xkb.idx_caps },
1319 { SDL_KMOD_LEVEL5, input->xkb.idx_mod3 },
1320 { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT, input->xkb.idx_mod3 | input->xkb.idx_shift },
1321 { SDL_KMOD_LEVEL5 | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_caps },
1322 { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_shift | input->xkb.idx_caps },
1323 { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE, input->xkb.idx_mod3 | input->xkb.idx_mod5 },
1324 { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT, input->xkb.idx_mod3 | input->xkb.idx_mod5 | input->xkb.idx_shift },
1325 { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_mod5 | input->xkb.idx_caps },
1326 { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_mod5 | input->xkb.idx_shift | input->xkb.idx_caps },
1327 };
1328
1329 if (!input->keyboard_is_virtual) {
1330 Wayland_Keymap keymap;
1331
1332 keymap.keymap = SDL_CreateKeymap();
1333 if (!keymap.keymap) {
1334 return;
1335 }
1336
1337 keymap.state = WAYLAND_xkb_state_new(input->xkb.keymap);
1338 if (!keymap.state) {
1339 SDL_SetError("failed to create XKB state");
1340 SDL_DestroyKeymap(keymap.keymap);
1341 return;
1342 }
1343
1344 for (int i = 0; i < SDL_arraysize(keymod_masks); ++i) {
1345 keymap.modstate = keymod_masks[i].sdl_mask;
1346 WAYLAND_xkb_state_update_mask(keymap.state,
1347 keymod_masks[i].xkb_mask & (input->xkb.idx_shift | input->xkb.idx_mod5 | input->xkb.idx_mod3), 0, keymod_masks[i].xkb_mask & input->xkb.idx_caps,
1348 0, 0, input->xkb.current_group);
1349 WAYLAND_xkb_keymap_key_for_each(input->xkb.keymap,
1350 Wayland_keymap_iter,
1351 &keymap);
1352 }
1353
1354 WAYLAND_xkb_state_unref(keymap.state);
1355 SDL_SetKeymap(keymap.keymap, true);
1356 } else {
1357 // Virtual keyboards use the default keymap.
1358 SDL_SetKeymap(NULL, true);
1359 }
1360}
1361
1362static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
1363 uint32_t format, int fd, uint32_t size)
1364{
1365 struct SDL_WaylandInput *input = data;
1366 char *map_str;
1367 const char *locale;
1368
1369 if (!data) {
1370 close(fd);
1371 return;
1372 }
1373
1374 if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
1375 close(fd);
1376 return;
1377 }
1378
1379 map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
1380 if (map_str == MAP_FAILED) {
1381 close(fd);
1382 return;
1383 }
1384
1385 if (input->xkb.keymap != NULL) {
1386 /* if there's already a keymap loaded, throw it away rather than leaking it before
1387 * parsing the new one
1388 */
1389 WAYLAND_xkb_keymap_unref(input->xkb.keymap);
1390 input->xkb.keymap = NULL;
1391 }
1392 input->xkb.keymap = WAYLAND_xkb_keymap_new_from_string(input->display->xkb_context,
1393 map_str,
1394 XKB_KEYMAP_FORMAT_TEXT_V1,
1395 0);
1396 munmap(map_str, size);
1397 close(fd);
1398
1399 if (!input->xkb.keymap) {
1400 SDL_SetError("failed to compile keymap");
1401 return;
1402 }
1403
1404#define GET_MOD_INDEX(mod) \
1405 WAYLAND_xkb_keymap_mod_get_index(input->xkb.keymap, XKB_MOD_NAME_##mod)
1406 input->xkb.idx_shift = 1 << GET_MOD_INDEX(SHIFT);
1407 input->xkb.idx_ctrl = 1 << GET_MOD_INDEX(CTRL);
1408 input->xkb.idx_alt = 1 << GET_MOD_INDEX(ALT);
1409 input->xkb.idx_gui = 1 << GET_MOD_INDEX(LOGO);
1410 input->xkb.idx_mod3 = 1 << GET_MOD_INDEX(MOD3);
1411 input->xkb.idx_mod5 = 1 << GET_MOD_INDEX(MOD5);
1412 input->xkb.idx_num = 1 << GET_MOD_INDEX(NUM);
1413 input->xkb.idx_caps = 1 << GET_MOD_INDEX(CAPS);
1414#undef GET_MOD_INDEX
1415
1416 if (input->xkb.state != NULL) {
1417 /* if there's already a state, throw it away rather than leaking it before
1418 * trying to create a new one with the new keymap.
1419 */
1420 WAYLAND_xkb_state_unref(input->xkb.state);
1421 input->xkb.state = NULL;
1422 }
1423 input->xkb.state = WAYLAND_xkb_state_new(input->xkb.keymap);
1424 if (!input->xkb.state) {
1425 SDL_SetError("failed to create XKB state");
1426 WAYLAND_xkb_keymap_unref(input->xkb.keymap);
1427 input->xkb.keymap = NULL;
1428 return;
1429 }
1430
1431 /*
1432 * Assume that a nameless layout implies a virtual keyboard with an arbitrary layout.
1433 * TODO: Use a better method of detection?
1434 */
1435 input->keyboard_is_virtual = WAYLAND_xkb_keymap_layout_get_name(input->xkb.keymap, 0) == NULL;
1436
1437 // Update the keymap if changed.
1438 if (input->xkb.current_group != XKB_GROUP_INVALID) {
1439 Wayland_UpdateKeymap(input);
1440 }
1441
1442 /*
1443 * See https://blogs.s-osg.org/compose-key-support-weston/
1444 * for further explanation on dead keys in Wayland.
1445 */
1446
1447 // Look up the preferred locale, falling back to "C" as default
1448 locale = SDL_getenv("LC_ALL");
1449 if (!locale) {
1450 locale = SDL_getenv("LC_CTYPE");
1451 if (!locale) {
1452 locale = SDL_getenv("LANG");
1453 if (!locale) {
1454 locale = "C";
1455 }
1456 }
1457 }
1458
1459 // Set up XKB compose table
1460 if (input->xkb.compose_table != NULL) {
1461 WAYLAND_xkb_compose_table_unref(input->xkb.compose_table);
1462 input->xkb.compose_table = NULL;
1463 }
1464 input->xkb.compose_table = WAYLAND_xkb_compose_table_new_from_locale(input->display->xkb_context,
1465 locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
1466 if (input->xkb.compose_table) {
1467 // Set up XKB compose state
1468 if (input->xkb.compose_state != NULL) {
1469 WAYLAND_xkb_compose_state_unref(input->xkb.compose_state);
1470 input->xkb.compose_state = NULL;
1471 }
1472 input->xkb.compose_state = WAYLAND_xkb_compose_state_new(input->xkb.compose_table,
1473 XKB_COMPOSE_STATE_NO_FLAGS);
1474 if (!input->xkb.compose_state) {
1475 SDL_SetError("could not create XKB compose state");
1476 WAYLAND_xkb_compose_table_unref(input->xkb.compose_table);
1477 input->xkb.compose_table = NULL;
1478 }
1479 }
1480}
1481
1482/*
1483 * Virtual keyboards can have arbitrary layouts, arbitrary scancodes/keycodes, etc...
1484 * Key presses from these devices must be looked up by their keysym value.
1485 */
1486static SDL_Scancode Wayland_GetScancodeForKey(struct SDL_WaylandInput *input, uint32_t key)
1487{
1488 SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
1489
1490 if (!input->keyboard_is_virtual) {
1491 scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, key);
1492 } else {
1493 const xkb_keysym_t *syms;
1494 if (WAYLAND_xkb_keymap_key_get_syms_by_level(input->xkb.keymap, key + 8, input->xkb.current_group, 0, &syms) > 0) {
1495 scancode = SDL_GetScancodeFromKeySym(syms[0], key);
1496 }
1497 }
1498
1499 return scancode;
1500}
1501
1502static void Wayland_ReconcileModifiers(struct SDL_WaylandInput *input, bool key_pressed)
1503{
1504 /* Handle explicit pressed modifier state. This will correct the modifier state
1505 * if common modifier keys were remapped and the modifiers presumed to be set
1506 * during a key press event were incorrect, or if the modifier was set to the
1507 * pressed state via means other than pressing the physical key.
1508 */
1509 if (!key_pressed) {
1510 if (input->xkb.wl_pressed_modifiers & input->xkb.idx_shift) {
1511 if (!(input->pressed_modifiers & SDL_KMOD_SHIFT)) {
1512 input->pressed_modifiers |= SDL_KMOD_SHIFT;
1513 }
1514 } else {
1515 input->pressed_modifiers &= ~SDL_KMOD_SHIFT;
1516 }
1517
1518 if (input->xkb.wl_pressed_modifiers & input->xkb.idx_ctrl) {
1519 if (!(input->pressed_modifiers & SDL_KMOD_CTRL)) {
1520 input->pressed_modifiers |= SDL_KMOD_CTRL;
1521 }
1522 } else {
1523 input->pressed_modifiers &= ~SDL_KMOD_CTRL;
1524 }
1525
1526 if (input->xkb.wl_pressed_modifiers & input->xkb.idx_alt) {
1527 if (!(input->pressed_modifiers & SDL_KMOD_ALT)) {
1528 input->pressed_modifiers |= SDL_KMOD_ALT;
1529 }
1530 } else {
1531 input->pressed_modifiers &= ~SDL_KMOD_ALT;
1532 }
1533
1534 if (input->xkb.wl_pressed_modifiers & input->xkb.idx_gui) {
1535 if (!(input->pressed_modifiers & SDL_KMOD_GUI)) {
1536 input->pressed_modifiers |= SDL_KMOD_GUI;
1537 }
1538 } else {
1539 input->pressed_modifiers &= ~SDL_KMOD_GUI;
1540 }
1541
1542 /* Note: This is not backwards: in the default keymap, Mod5 is typically
1543 * level 3 shift, and Mod3 is typically level 5 shift.
1544 */
1545 if (input->xkb.wl_pressed_modifiers & input->xkb.idx_mod3) {
1546 if (!(input->pressed_modifiers & SDL_KMOD_LEVEL5)) {
1547 input->pressed_modifiers |= SDL_KMOD_LEVEL5;
1548 }
1549 } else {
1550 input->pressed_modifiers &= ~SDL_KMOD_LEVEL5;
1551 }
1552
1553 if (input->xkb.wl_pressed_modifiers & input->xkb.idx_mod5) {
1554 if (!(input->pressed_modifiers & SDL_KMOD_MODE)) {
1555 input->pressed_modifiers |= SDL_KMOD_MODE;
1556 }
1557 } else {
1558 input->pressed_modifiers &= ~SDL_KMOD_MODE;
1559 }
1560 }
1561
1562 /* If a latch or lock was activated by a keypress, the latch/lock will
1563 * be tied to the specific left/right key that initiated it. Otherwise,
1564 * the ambiguous left/right combo is used.
1565 *
1566 * The modifier will remain active until the latch/lock is released by
1567 * the system.
1568 */
1569 if (input->xkb.wl_locked_modifiers & input->xkb.idx_shift) {
1570 if (input->pressed_modifiers & SDL_KMOD_SHIFT) {
1571 input->locked_modifiers &= ~SDL_KMOD_SHIFT;
1572 input->locked_modifiers |= (input->pressed_modifiers & SDL_KMOD_SHIFT);
1573 } else if (!(input->locked_modifiers & SDL_KMOD_SHIFT)) {
1574 input->locked_modifiers |= SDL_KMOD_SHIFT;
1575 }
1576 } else {
1577 input->locked_modifiers &= ~SDL_KMOD_SHIFT;
1578 }
1579
1580 if (input->xkb.wl_locked_modifiers & input->xkb.idx_ctrl) {
1581 if (input->pressed_modifiers & SDL_KMOD_CTRL) {
1582 input->locked_modifiers &= ~SDL_KMOD_CTRL;
1583 input->locked_modifiers |= (input->pressed_modifiers & SDL_KMOD_CTRL);
1584 } else if (!(input->locked_modifiers & SDL_KMOD_CTRL)) {
1585 input->locked_modifiers |= SDL_KMOD_CTRL;
1586 }
1587 } else {
1588 input->locked_modifiers &= ~SDL_KMOD_CTRL;
1589 }
1590
1591 if (input->xkb.wl_locked_modifiers & input->xkb.idx_alt) {
1592 if (input->pressed_modifiers & SDL_KMOD_ALT) {
1593 input->locked_modifiers &= ~SDL_KMOD_ALT;
1594 input->locked_modifiers |= (input->pressed_modifiers & SDL_KMOD_ALT);
1595 } else if (!(input->locked_modifiers & SDL_KMOD_ALT)) {
1596 input->locked_modifiers |= SDL_KMOD_ALT;
1597 }
1598 } else {
1599 input->locked_modifiers &= ~SDL_KMOD_ALT;
1600 }
1601
1602 if (input->xkb.wl_locked_modifiers & input->xkb.idx_gui) {
1603 if (input->pressed_modifiers & SDL_KMOD_GUI) {
1604 input->locked_modifiers &= ~SDL_KMOD_GUI;
1605 input->locked_modifiers |= (input->pressed_modifiers & SDL_KMOD_GUI);
1606 } else if (!(input->locked_modifiers & SDL_KMOD_GUI)) {
1607 input->locked_modifiers |= SDL_KMOD_GUI;
1608 }
1609 } else {
1610 input->locked_modifiers &= ~SDL_KMOD_GUI;
1611 }
1612
1613 // As above, this is correct: Mod3 is typically level 5 shift, and Mod5 is typically level 3 shift.
1614 if (input->xkb.wl_locked_modifiers & input->xkb.idx_mod3) {
1615 input->locked_modifiers |= SDL_KMOD_LEVEL5;
1616 } else {
1617 input->locked_modifiers &= ~SDL_KMOD_LEVEL5;
1618 }
1619
1620 if (input->xkb.wl_locked_modifiers & input->xkb.idx_mod5) {
1621 input->locked_modifiers |= SDL_KMOD_MODE;
1622 } else {
1623 input->locked_modifiers &= ~SDL_KMOD_MODE;
1624 }
1625
1626 // Capslock and Numlock can only be locked, not pressed.
1627 if (input->xkb.wl_locked_modifiers & input->xkb.idx_caps) {
1628 input->locked_modifiers |= SDL_KMOD_CAPS;
1629 } else {
1630 input->locked_modifiers &= ~SDL_KMOD_CAPS;
1631 }
1632
1633 if (input->xkb.wl_locked_modifiers & input->xkb.idx_num) {
1634 input->locked_modifiers |= SDL_KMOD_NUM;
1635 } else {
1636 input->locked_modifiers &= ~SDL_KMOD_NUM;
1637 }
1638
1639 SDL_SetModState(input->pressed_modifiers | input->locked_modifiers);
1640}
1641
1642static void Wayland_HandleModifierKeys(struct SDL_WaylandInput *input, SDL_Scancode scancode, bool pressed)
1643{
1644 const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);
1645 SDL_Keymod mod;
1646
1647 /* SDL clients expect modifier state to be activated at the same time as the
1648 * source keypress, so we set pressed modifier state with the usual modifier
1649 * keys here, as the explicit modifier event won't arrive until after the
1650 * keypress event. If this is wrong, it will be corrected when the explicit
1651 * modifier state is sent at a later time.
1652 */
1653 switch (keycode) {
1654 case SDLK_LSHIFT:
1655 mod = SDL_KMOD_LSHIFT;
1656 break;
1657 case SDLK_RSHIFT:
1658 mod = SDL_KMOD_RSHIFT;
1659 break;
1660 case SDLK_LCTRL:
1661 mod = SDL_KMOD_LCTRL;
1662 break;
1663 case SDLK_RCTRL:
1664 mod = SDL_KMOD_RCTRL;
1665 break;
1666 case SDLK_LALT:
1667 mod = SDL_KMOD_LALT;
1668 break;
1669 case SDLK_RALT:
1670 mod = SDL_KMOD_RALT;
1671 break;
1672 case SDLK_LGUI:
1673 mod = SDL_KMOD_LGUI;
1674 break;
1675 case SDLK_RGUI:
1676 mod = SDL_KMOD_RGUI;
1677 break;
1678 case SDLK_MODE:
1679 mod = SDL_KMOD_MODE;
1680 break;
1681 case SDLK_LEVEL5_SHIFT:
1682 mod = SDL_KMOD_LEVEL5;
1683 break;
1684 default:
1685 return;
1686 }
1687
1688 if (pressed) {
1689 input->pressed_modifiers |= mod;
1690 } else {
1691 input->pressed_modifiers &= ~mod;
1692 }
1693
1694 Wayland_ReconcileModifiers(input, true);
1695}
1696
1697static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
1698 uint32_t serial, struct wl_surface *surface,
1699 struct wl_array *keys)
1700{
1701 struct SDL_WaylandInput *input = data;
1702 SDL_WindowData *window;
1703 uint32_t *key;
1704
1705 if (!surface) {
1706 // enter event for a window we've just destroyed
1707 return;
1708 }
1709
1710 window = Wayland_GetWindowDataForOwnedSurface(surface);
1711
1712 if (!window) {
1713 return;
1714 }
1715
1716 input->keyboard_focus = window;
1717 window->keyboard_device = input;
1718
1719 // Restore the keyboard focus to the child popup that was holding it
1720 SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window->sdlwindow);
1721
1722#ifdef SDL_USE_IME
1723 if (!input->text_input) {
1724 SDL_IME_SetFocus(true);
1725 }
1726#endif
1727
1728 Uint64 timestamp = SDL_GetTicksNS();
1729 window->last_focus_event_time_ns = timestamp;
1730
1731 wl_array_for_each (key, keys) {
1732 const SDL_Scancode scancode = Wayland_GetScancodeForKey(input, *key);
1733 const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);
1734
1735 switch (keycode) {
1736 case SDLK_LSHIFT:
1737 case SDLK_RSHIFT:
1738 case SDLK_LCTRL:
1739 case SDLK_RCTRL:
1740 case SDLK_LALT:
1741 case SDLK_RALT:
1742 case SDLK_LGUI:
1743 case SDLK_RGUI:
1744 case SDLK_MODE:
1745 case SDLK_LEVEL5_SHIFT:
1746 Wayland_HandleModifierKeys(input, scancode, true);
1747 SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, *key, scancode, true);
1748 break;
1749 default:
1750 break;
1751 }
1752 }
1753}
1754
1755static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
1756 uint32_t serial, struct wl_surface *surface)
1757{
1758 struct SDL_WaylandInput *input = data;
1759 SDL_WindowData *wind;
1760 SDL_Window *window = NULL;
1761
1762 if (!surface) {
1763 return;
1764 }
1765
1766 wind = Wayland_GetWindowDataForOwnedSurface(surface);
1767 if (!wind) {
1768 return;
1769 }
1770
1771 wind->keyboard_device = NULL;
1772 window = wind->sdlwindow;
1773
1774 // Stop key repeat before clearing keyboard focus
1775 keyboard_repeat_clear(&input->keyboard_repeat);
1776
1777 // This will release any keys still pressed
1778 SDL_SetKeyboardFocus(NULL);
1779 input->keyboard_focus = NULL;
1780
1781 // Clear the pressed modifiers.
1782 input->pressed_modifiers = SDL_KMOD_NONE;
1783
1784#ifdef SDL_USE_IME
1785 if (!input->text_input) {
1786 SDL_IME_SetFocus(false);
1787 }
1788#endif
1789
1790 /* If the surface had a pointer leave event while still having active touch events, it retained mouse focus.
1791 * Clear it now if all touch events are raised.
1792 */
1793 if (!input->pointer_focus && SDL_GetMouseFocus() == window && !Wayland_SurfaceHasActiveTouches(surface)) {
1794 SDL_SetMouseFocus(NULL);
1795 }
1796}
1797
1798static bool keyboard_input_get_text(char text[8], const struct SDL_WaylandInput *input, uint32_t key, bool down, bool *handled_by_ime)
1799{
1800 SDL_WindowData *window = input->keyboard_focus;
1801 const xkb_keysym_t *syms;
1802 xkb_keysym_t sym;
1803
1804 if (!window || window->keyboard_device != input || !input->xkb.state) {
1805 return false;
1806 }
1807
1808 // TODO: Can this happen?
1809 if (WAYLAND_xkb_state_key_get_syms(input->xkb.state, key + 8, &syms) != 1) {
1810 return false;
1811 }
1812 sym = syms[0];
1813
1814#ifdef SDL_USE_IME
1815 if (SDL_IME_ProcessKeyEvent(sym, key + 8, down)) {
1816 if (handled_by_ime) {
1817 *handled_by_ime = true;
1818 }
1819 return true;
1820 }
1821#endif
1822
1823 if (!down) {
1824 return false;
1825 }
1826
1827 if (input->xkb.compose_state && WAYLAND_xkb_compose_state_feed(input->xkb.compose_state, sym) == XKB_COMPOSE_FEED_ACCEPTED) {
1828 switch (WAYLAND_xkb_compose_state_get_status(input->xkb.compose_state)) {
1829 case XKB_COMPOSE_COMPOSING:
1830 if (handled_by_ime) {
1831 *handled_by_ime = true;
1832 }
1833 return true;
1834 case XKB_COMPOSE_CANCELLED:
1835 default:
1836 sym = XKB_KEY_NoSymbol;
1837 break;
1838 case XKB_COMPOSE_NOTHING:
1839 break;
1840 case XKB_COMPOSE_COMPOSED:
1841 sym = WAYLAND_xkb_compose_state_get_one_sym(input->xkb.compose_state);
1842 break;
1843 }
1844 }
1845
1846 return WAYLAND_xkb_keysym_to_utf8(sym, text, 8) > 0;
1847}
1848
1849static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
1850 uint32_t serial, uint32_t time, uint32_t key,
1851 uint32_t state_w)
1852{
1853 struct SDL_WaylandInput *input = data;
1854 enum wl_keyboard_key_state state = state_w;
1855 char text[8];
1856 bool has_text = false;
1857 bool handled_by_ime = false;
1858 const Uint64 timestamp_raw_ns = Wayland_GetKeyboardTimestampRaw(input, time);
1859
1860 Wayland_UpdateImplicitGrabSerial(input, serial);
1861
1862 if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
1863 SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
1864 if (keyboard_focus && SDL_TextInputActive(keyboard_focus)) {
1865 has_text = keyboard_input_get_text(text, input, key, true, &handled_by_ime);
1866 }
1867 } else {
1868 if (keyboard_repeat_key_is_set(&input->keyboard_repeat, key)) {
1869 /* Send any due key repeat events before stopping the repeat and generating the key up event.
1870 * Compute time based on the Wayland time, as it reports when the release event happened.
1871 * Using SDL_GetTicks would be wrong, as it would report when the release event is processed,
1872 * which may be off if the application hasn't pumped events for a while.
1873 */
1874 keyboard_repeat_handle(&input->keyboard_repeat, timestamp_raw_ns - input->keyboard_repeat.wl_press_time_ns);
1875 keyboard_repeat_clear(&input->keyboard_repeat);
1876 }
1877 keyboard_input_get_text(text, input, key, false, &handled_by_ime);
1878 }
1879
1880 const SDL_Scancode scancode = Wayland_GetScancodeForKey(input, key);
1881 Wayland_HandleModifierKeys(input, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
1882 Uint64 timestamp = Wayland_GetKeyboardTimestamp(input, time);
1883
1884 SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
1885
1886 if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
1887 if (has_text && !(SDL_GetModState() & SDL_KMOD_CTRL)) {
1888 if (!handled_by_ime) {
1889 SDL_SendKeyboardText(text);
1890 }
1891 }
1892 if (input->xkb.keymap && WAYLAND_xkb_keymap_key_repeats(input->xkb.keymap, key + 8)) {
1893 keyboard_repeat_set(&input->keyboard_repeat, input->keyboard_id, key, timestamp_raw_ns, scancode, has_text, text);
1894 }
1895 }
1896}
1897
1898static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard,
1899 uint32_t serial, uint32_t mods_depressed,
1900 uint32_t mods_latched, uint32_t mods_locked,
1901 uint32_t group)
1902{
1903 struct SDL_WaylandInput *input = data;
1904
1905 if (input->xkb.state == NULL) {
1906 /* if we get a modifier notification before the keymap, there's nothing we can do with the information
1907 */
1908 return;
1909 }
1910
1911 WAYLAND_xkb_state_update_mask(input->xkb.state, mods_depressed, mods_latched,
1912 mods_locked, 0, 0, group);
1913
1914 input->xkb.wl_pressed_modifiers = mods_depressed;
1915 input->xkb.wl_locked_modifiers = mods_latched | mods_locked;
1916
1917 Wayland_ReconcileModifiers(input, false);
1918
1919 // If a key is repeating, update the text to apply the modifier.
1920 if (keyboard_repeat_is_set(&input->keyboard_repeat)) {
1921 char text[8];
1922 const uint32_t key = keyboard_repeat_get_key(&input->keyboard_repeat);
1923
1924 if (keyboard_input_get_text(text, input, key, true, NULL)) {
1925 keyboard_repeat_set_text(&input->keyboard_repeat, text);
1926 }
1927 }
1928
1929 if (group == input->xkb.current_group) {
1930 return;
1931 }
1932
1933 // The layout changed, remap and fire an event. Virtual keyboards use the default keymap.
1934 input->xkb.current_group = group;
1935 Wayland_UpdateKeymap(input);
1936}
1937
1938static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
1939 int32_t rate, int32_t delay)
1940{
1941 struct SDL_WaylandInput *input = data;
1942 input->keyboard_repeat.repeat_rate = SDL_clamp(rate, 0, 1000);
1943 input->keyboard_repeat.repeat_delay_ms = delay;
1944 input->keyboard_repeat.is_initialized = true;
1945}
1946
1947static const struct wl_keyboard_listener keyboard_listener = {
1948 keyboard_handle_keymap,
1949 keyboard_handle_enter,
1950 keyboard_handle_leave,
1951 keyboard_handle_key,
1952 keyboard_handle_modifiers,
1953 keyboard_handle_repeat_info, // Version 4
1954};
1955
1956void Wayland_input_init_relative_pointer(SDL_VideoData *d)
1957{
1958 struct SDL_WaylandInput *input = d->input;
1959
1960 if (!d->relative_pointer_manager) {
1961 return;
1962 }
1963
1964 if (input->pointer && !input->relative_pointer) {
1965 input->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(input->display->relative_pointer_manager, input->pointer);
1966 zwp_relative_pointer_v1_add_listener(input->relative_pointer,
1967 &relative_pointer_listener,
1968 input);
1969 }
1970}
1971
1972static void seat_handle_capabilities(void *data, struct wl_seat *seat,
1973 enum wl_seat_capability caps)
1974{
1975 struct SDL_WaylandInput *input = data;
1976
1977 if ((caps & WL_SEAT_CAPABILITY_POINTER) && !input->pointer) {
1978 input->pointer = wl_seat_get_pointer(seat);
1979 SDL_memset(&input->pointer_curr_axis_info, 0, sizeof(input->pointer_curr_axis_info));
1980 input->display->pointer = input->pointer;
1981
1982 Wayland_CreateCursorShapeDevice(input);
1983
1984 wl_pointer_set_user_data(input->pointer, input);
1985 wl_pointer_add_listener(input->pointer, &pointer_listener, input);
1986
1987 Wayland_input_init_relative_pointer(input->display);
1988
1989 input->pointer_id = SDL_GetNextObjectID();
1990 SDL_AddMouse(input->pointer_id, WAYLAND_DEFAULT_POINTER_NAME, !input->display->initializing);
1991 } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->pointer) {
1992 if (input->relative_pointer) {
1993 zwp_relative_pointer_v1_destroy(input->relative_pointer);
1994 input->relative_pointer = NULL;
1995 }
1996 if (input->cursor_shape) {
1997 wp_cursor_shape_device_v1_destroy(input->cursor_shape);
1998 input->cursor_shape = NULL;
1999 }
2000 if (wl_pointer_get_version(input->pointer) >= WL_POINTER_RELEASE_SINCE_VERSION) {
2001 wl_pointer_release(input->pointer);
2002 } else {
2003 wl_pointer_destroy(input->pointer);
2004 }
2005 input->pointer = NULL;
2006 input->display->pointer = NULL;
2007
2008 SDL_RemoveMouse(input->pointer_id, true);
2009 input->pointer_id = 0;
2010 }
2011
2012 if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch) {
2013 input->touch = wl_seat_get_touch(seat);
2014 SDL_AddTouch((SDL_TouchID)(uintptr_t)input->touch, SDL_TOUCH_DEVICE_DIRECT, "wayland_touch");
2015 wl_touch_set_user_data(input->touch, input);
2016 wl_touch_add_listener(input->touch, &touch_listener,
2017 input);
2018 } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->touch) {
2019 SDL_DelTouch((SDL_TouchID)(intptr_t)input->touch);
2020 wl_touch_destroy(input->touch);
2021 input->touch = NULL;
2022 }
2023
2024 if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard) {
2025 input->keyboard = wl_seat_get_keyboard(seat);
2026 wl_keyboard_set_user_data(input->keyboard, input);
2027 wl_keyboard_add_listener(input->keyboard, &keyboard_listener,
2028 input);
2029
2030 input->keyboard_id = SDL_GetNextObjectID();
2031 SDL_AddKeyboard(input->keyboard_id, WAYLAND_DEFAULT_KEYBOARD_NAME, !input->display->initializing);
2032 } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard) {
2033 wl_keyboard_destroy(input->keyboard);
2034 input->keyboard = NULL;
2035
2036 SDL_RemoveKeyboard(input->keyboard_id, true);
2037 input->keyboard_id = 0;
2038 }
2039
2040 Wayland_RegisterTimestampListeners(input);
2041}
2042
2043static void seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name)
2044{
2045 // unimplemented
2046}
2047
2048static const struct wl_seat_listener seat_listener = {
2049 seat_handle_capabilities,
2050 seat_handle_name, // Version 2
2051};
2052
2053static void data_source_handle_target(void *data, struct wl_data_source *wl_data_source,
2054 const char *mime_type)
2055{
2056}
2057
2058static void data_source_handle_send(void *data, struct wl_data_source *wl_data_source,
2059 const char *mime_type, int32_t fd)
2060{
2061 Wayland_data_source_send((SDL_WaylandDataSource *)data, mime_type, fd);
2062}
2063
2064static void data_source_handle_cancelled(void *data, struct wl_data_source *wl_data_source)
2065{
2066 SDL_WaylandDataSource *source = data;
2067 if (source) {
2068 Wayland_data_source_destroy(source);
2069 }
2070}
2071
2072static void data_source_handle_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source)
2073{
2074}
2075
2076static void data_source_handle_dnd_finished(void *data, struct wl_data_source *wl_data_source)
2077{
2078}
2079
2080static void data_source_handle_action(void *data, struct wl_data_source *wl_data_source,
2081 uint32_t dnd_action)
2082{
2083}
2084
2085static const struct wl_data_source_listener data_source_listener = {
2086 data_source_handle_target,
2087 data_source_handle_send,
2088 data_source_handle_cancelled,
2089 data_source_handle_dnd_drop_performed, // Version 3
2090 data_source_handle_dnd_finished, // Version 3
2091 data_source_handle_action, // Version 3
2092};
2093
2094static void primary_selection_source_send(void *data, struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1,
2095 const char *mime_type, int32_t fd)
2096{
2097 Wayland_primary_selection_source_send((SDL_WaylandPrimarySelectionSource *)data,
2098 mime_type, fd);
2099}
2100
2101static void primary_selection_source_cancelled(void *data, struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1)
2102{
2103 Wayland_primary_selection_source_destroy(data);
2104}
2105
2106static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
2107 primary_selection_source_send,
2108 primary_selection_source_cancelled,
2109};
2110
2111SDL_WaylandDataSource *Wayland_data_source_create(SDL_VideoDevice *_this)
2112{
2113 SDL_WaylandDataSource *data_source = NULL;
2114 SDL_VideoData *driver_data = NULL;
2115 struct wl_data_source *id = NULL;
2116
2117 if (!_this || !_this->internal) {
2118 SDL_SetError("Video driver uninitialized");
2119 } else {
2120 driver_data = _this->internal;
2121
2122 if (driver_data->data_device_manager) {
2123 id = wl_data_device_manager_create_data_source(
2124 driver_data->data_device_manager);
2125 }
2126
2127 if (!id) {
2128 SDL_SetError("Wayland unable to create data source");
2129 } else {
2130 data_source = SDL_calloc(1, sizeof(*data_source));
2131 if (!data_source) {
2132 wl_data_source_destroy(id);
2133 } else {
2134 data_source->source = id;
2135 wl_data_source_set_user_data(id, data_source);
2136 wl_data_source_add_listener(id, &data_source_listener,
2137 data_source);
2138 }
2139 }
2140 }
2141 return data_source;
2142}
2143
2144SDL_WaylandPrimarySelectionSource *Wayland_primary_selection_source_create(SDL_VideoDevice *_this)
2145{
2146 SDL_WaylandPrimarySelectionSource *primary_selection_source = NULL;
2147 SDL_VideoData *driver_data = NULL;
2148 struct zwp_primary_selection_source_v1 *id = NULL;
2149
2150 if (!_this || !_this->internal) {
2151 SDL_SetError("Video driver uninitialized");
2152 } else {
2153 driver_data = _this->internal;
2154
2155 if (driver_data->primary_selection_device_manager) {
2156 id = zwp_primary_selection_device_manager_v1_create_source(
2157 driver_data->primary_selection_device_manager);
2158 }
2159
2160 if (!id) {
2161 SDL_SetError("Wayland unable to create primary selection source");
2162 } else {
2163 primary_selection_source = SDL_calloc(1, sizeof(*primary_selection_source));
2164 if (!primary_selection_source) {
2165 zwp_primary_selection_source_v1_destroy(id);
2166 } else {
2167 primary_selection_source->source = id;
2168 zwp_primary_selection_source_v1_add_listener(id, &primary_selection_source_listener,
2169 primary_selection_source);
2170 }
2171 }
2172 }
2173 return primary_selection_source;
2174}
2175
2176static void data_offer_handle_offer(void *data, struct wl_data_offer *wl_data_offer,
2177 const char *mime_type)
2178{
2179 SDL_WaylandDataOffer *offer = data;
2180 Wayland_data_offer_add_mime(offer, mime_type);
2181 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2182 ". In wl_data_offer_listener . data_offer_handle_offer on data_offer 0x%08x for MIME '%s'",
2183 (wl_data_offer ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)wl_data_offer) : -1),
2184 mime_type);
2185}
2186
2187static void data_offer_handle_source_actions(void *data, struct wl_data_offer *wl_data_offer,
2188 uint32_t source_actions)
2189{
2190 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2191 ". In wl_data_offer_listener . data_offer_handle_source_actions on data_offer 0x%08x for Source Actions '%d'",
2192 (wl_data_offer ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)wl_data_offer) : -1),
2193 source_actions);
2194}
2195
2196static void data_offer_handle_actions(void *data, struct wl_data_offer *wl_data_offer,
2197 uint32_t dnd_action)
2198{
2199 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2200 ". In wl_data_offer_listener . data_offer_handle_actions on data_offer 0x%08x for DND Actions '%d'",
2201 (wl_data_offer ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)wl_data_offer) : -1),
2202 dnd_action);
2203}
2204
2205static const struct wl_data_offer_listener data_offer_listener = {
2206 data_offer_handle_offer,
2207 data_offer_handle_source_actions, // Version 3
2208 data_offer_handle_actions, // Version 3
2209};
2210
2211static void primary_selection_offer_handle_offer(void *data, struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1,
2212 const char *mime_type)
2213{
2214 SDL_WaylandPrimarySelectionOffer *offer = data;
2215 Wayland_primary_selection_offer_add_mime(offer, mime_type);
2216 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2217 ". In zwp_primary_selection_offer_v1_listener . primary_selection_offer_handle_offer on primary_selection_offer 0x%08x for MIME '%s'",
2218 (zwp_primary_selection_offer_v1 ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)zwp_primary_selection_offer_v1) : -1),
2219 mime_type);
2220}
2221
2222static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
2223 primary_selection_offer_handle_offer,
2224};
2225
2226static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_data_device,
2227 struct wl_data_offer *id)
2228{
2229 SDL_WaylandDataOffer *data_offer = SDL_calloc(1, sizeof(*data_offer));
2230 if (data_offer) {
2231 data_offer->offer = id;
2232 data_offer->data_device = data;
2233 WAYLAND_wl_list_init(&(data_offer->mimes));
2234 wl_data_offer_set_user_data(id, data_offer);
2235 wl_data_offer_add_listener(id, &data_offer_listener, data_offer);
2236 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2237 ". In wl_data_device_listener . data_device_handle_data_offer on data_offer 0x%08x",
2238 (id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)id) : -1));
2239 }
2240}
2241
2242static void data_device_handle_enter(void *data, struct wl_data_device *wl_data_device,
2243 uint32_t serial, struct wl_surface *surface,
2244 wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id)
2245{
2246 SDL_WaylandDataDevice *data_device = data;
2247 data_device->has_mime_file = false;
2248 data_device->has_mime_text = false;
2249 uint32_t dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
2250
2251 data_device->drag_serial = serial;
2252
2253 if (id) {
2254 data_device->drag_offer = wl_data_offer_get_user_data(id);
2255
2256 // TODO: SDL Support more mime types
2257#ifdef SDL_USE_LIBDBUS
2258 if (Wayland_data_offer_has_mime(data_device->drag_offer, FILE_PORTAL_MIME)) {
2259 data_device->has_mime_file = true;
2260 wl_data_offer_accept(id, serial, FILE_PORTAL_MIME);
2261 }
2262#endif
2263 if (Wayland_data_offer_has_mime(data_device->drag_offer, FILE_MIME)) {
2264 data_device->has_mime_file = true;
2265 wl_data_offer_accept(id, serial, FILE_MIME);
2266 }
2267
2268 if (Wayland_data_offer_has_mime(data_device->drag_offer, TEXT_MIME)) {
2269 data_device->has_mime_text = true;
2270 wl_data_offer_accept(id, serial, TEXT_MIME);
2271 }
2272
2273 // SDL only supports "copy" style drag and drop
2274 if (data_device->has_mime_file || data_device->has_mime_text) {
2275 dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
2276 } else {
2277 // drag_mime is NULL this will decline the offer
2278 wl_data_offer_accept(id, serial, NULL);
2279 }
2280 if (wl_data_offer_get_version(data_device->drag_offer->offer) >=
2281 WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION) {
2282 wl_data_offer_set_actions(data_device->drag_offer->offer,
2283 dnd_action, dnd_action);
2284 }
2285
2286 // find the current window
2287 if (surface) {
2288 SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
2289 if (window) {
2290 data_device->dnd_window = window->sdlwindow;
2291 const float dx = (float)wl_fixed_to_double(x);
2292 const float dy = (float)wl_fixed_to_double(y);
2293 SDL_SendDropPosition(data_device->dnd_window, dx, dy);
2294 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2295 ". In wl_data_device_listener . data_device_handle_enter on data_offer 0x%08x at %d x %d into window %d for serial %d",
2296 WAYLAND_wl_proxy_get_id((struct wl_proxy *)id),
2297 wl_fixed_to_int(x), wl_fixed_to_int(y), SDL_GetWindowID(data_device->dnd_window), serial);
2298 } else {
2299 data_device->dnd_window = NULL;
2300 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2301 ". In wl_data_device_listener . data_device_handle_enter on data_offer 0x%08x at %d x %d for serial %d",
2302 WAYLAND_wl_proxy_get_id((struct wl_proxy *)id),
2303 wl_fixed_to_int(x), wl_fixed_to_int(y), serial);
2304 }
2305 } else {
2306 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2307 ". In wl_data_device_listener . data_device_handle_enter on data_offer 0x%08x at %d x %d for serial %d",
2308 WAYLAND_wl_proxy_get_id((struct wl_proxy *)id),
2309 wl_fixed_to_int(x), wl_fixed_to_int(y), serial);
2310 }
2311 } else {
2312 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2313 ". In wl_data_device_listener . data_device_handle_enter on data_offer 0x%08x at %d x %d for serial %d",
2314 -1, wl_fixed_to_int(x), wl_fixed_to_int(y), serial);
2315 }
2316}
2317
2318static void data_device_handle_leave(void *data, struct wl_data_device *wl_data_device)
2319{
2320 SDL_WaylandDataDevice *data_device = data;
2321
2322 if (data_device->drag_offer) {
2323 if (data_device->dnd_window) {
2324 SDL_SendDropComplete(data_device->dnd_window);
2325 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2326 ". In wl_data_device_listener . data_device_handle_leave on data_offer 0x%08x from window %d for serial %d",
2327 WAYLAND_wl_proxy_get_id((struct wl_proxy *)data_device->drag_offer->offer),
2328 SDL_GetWindowID(data_device->dnd_window), data_device->drag_serial);
2329 } else {
2330 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2331 ". In wl_data_device_listener . data_device_handle_leave on data_offer 0x%08x for serial %d",
2332 WAYLAND_wl_proxy_get_id((struct wl_proxy *)data_device->drag_offer->offer),
2333 data_device->drag_serial);
2334 }
2335 Wayland_data_offer_destroy(data_device->drag_offer);
2336 data_device->drag_offer = NULL;
2337 } else {
2338 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2339 ". In wl_data_device_listener . data_device_handle_leave on data_offer 0x%08x for serial %d",
2340 -1, -1);
2341 }
2342 data_device->has_mime_file = false;
2343 data_device->has_mime_text = false;
2344}
2345
2346static void data_device_handle_motion(void *data, struct wl_data_device *wl_data_device,
2347 uint32_t time, wl_fixed_t x, wl_fixed_t y)
2348{
2349 SDL_WaylandDataDevice *data_device = data;
2350
2351 if (data_device->drag_offer && data_device->dnd_window && (data_device->has_mime_file || data_device->has_mime_text)) {
2352 const float dx = (float)wl_fixed_to_double(x);
2353 const float dy = (float)wl_fixed_to_double(y);
2354
2355 /* XXX: Send the filename here if the event system ever starts passing it though.
2356 * Any future implementation should cache the filenames, as otherwise this could
2357 * hammer the DBus interface hundreds or even thousands of times per second.
2358 */
2359 SDL_SendDropPosition(data_device->dnd_window, dx, dy);
2360 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2361 ". In wl_data_device_listener . data_device_handle_motion on data_offer 0x%08x at %d x %d in window %d serial %d",
2362 WAYLAND_wl_proxy_get_id((struct wl_proxy *)data_device->drag_offer->offer),
2363 wl_fixed_to_int(x), wl_fixed_to_int(y),
2364 SDL_GetWindowID(data_device->dnd_window), data_device->drag_serial);
2365 } else {
2366 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2367 ". In wl_data_device_listener . data_device_handle_motion on data_offer 0x%08x at %d x %d serial %d",
2368 -1, wl_fixed_to_int(x), wl_fixed_to_int(y), -1);
2369 }
2370}
2371
2372static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_device)
2373{
2374 SDL_WaylandDataDevice *data_device = data;
2375
2376 if (data_device->drag_offer && data_device->dnd_window && (data_device->has_mime_file || data_device->has_mime_text)) {
2377 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2378 ". In wl_data_device_listener . data_device_handle_drop on data_offer 0x%08x in window %d serial %d",
2379 WAYLAND_wl_proxy_get_id((struct wl_proxy *)data_device->drag_offer->offer),
2380 SDL_GetWindowID(data_device->dnd_window), data_device->drag_serial);
2381 // TODO: SDL Support more mime types
2382 size_t length;
2383 bool drop_handled = false;
2384#ifdef SDL_USE_LIBDBUS
2385 if (Wayland_data_offer_has_mime(data_device->drag_offer, FILE_PORTAL_MIME)) {
2386 void *buffer = Wayland_data_offer_receive(data_device->drag_offer,
2387 FILE_PORTAL_MIME, &length);
2388 if (buffer) {
2389 SDL_DBusContext *dbus = SDL_DBus_GetContext();
2390 if (dbus) {
2391 int path_count = 0;
2392 char **paths = SDL_DBus_DocumentsPortalRetrieveFiles(buffer, &path_count);
2393 // If dropped files contain a directory the list is empty
2394 if (paths && path_count > 0) {
2395 int i;
2396 for (i = 0; i < path_count; i++) {
2397 SDL_SendDropFile(data_device->dnd_window, NULL, paths[i]);
2398 }
2399 dbus->free_string_array(paths);
2400 SDL_SendDropComplete(data_device->dnd_window);
2401 drop_handled = true;
2402 }
2403 }
2404 SDL_free(buffer);
2405 }
2406 }
2407#endif
2408 /* If XDG document portal fails fallback.
2409 * When running a flatpak sandbox this will most likely be a list of
2410 * non paths that are not visible to the application
2411 */
2412 if (!drop_handled) {
2413 const char *mime_type = data_device->has_mime_file ? FILE_MIME : (data_device->has_mime_text ? TEXT_MIME : "");
2414 void *buffer = Wayland_data_offer_receive(data_device->drag_offer,
2415 mime_type, &length);
2416 if (data_device->has_mime_file) {
2417 if (buffer) {
2418 char *saveptr = NULL;
2419 char *token = SDL_strtok_r((char *)buffer, "\r\n", &saveptr);
2420 while (token) {
2421 if (SDL_URIToLocal(token, token) >= 0) {
2422 SDL_SendDropFile(data_device->dnd_window, NULL, token);
2423 }
2424 token = SDL_strtok_r(NULL, "\r\n", &saveptr);
2425 }
2426 SDL_free(buffer);
2427 SDL_SendDropComplete(data_device->dnd_window);
2428 } else {
2429 SDL_SendDropComplete(data_device->dnd_window);
2430 }
2431 drop_handled = true;
2432 } else if (data_device->has_mime_text) {
2433 if (buffer) {
2434 char *saveptr = NULL;
2435 char *token = SDL_strtok_r((char *)buffer, "\r\n", &saveptr);
2436 while (token) {
2437 SDL_SendDropText(data_device->dnd_window, token);
2438 token = SDL_strtok_r(NULL, "\r\n", &saveptr);
2439 }
2440 SDL_free(buffer);
2441 SDL_SendDropComplete(data_device->dnd_window);
2442 } else {
2443 /* Even though there has been a valid data offer,
2444 * and there have been valid Enter, Motion, and Drop callbacks,
2445 * Wayland_data_offer_receive may return an empty buffer,
2446 * because the data is actually in the primary selection device,
2447 * not in the data device.
2448 */
2449 SDL_SendDropComplete(data_device->dnd_window);
2450 }
2451 drop_handled = true;
2452 }
2453 }
2454
2455 if (drop_handled && wl_data_offer_get_version(data_device->drag_offer->offer) >= WL_DATA_OFFER_FINISH_SINCE_VERSION) {
2456 wl_data_offer_finish(data_device->drag_offer->offer);
2457 }
2458 } else {
2459 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2460 ". In wl_data_device_listener . data_device_handle_drop on data_offer 0x%08x serial %d",
2461 -1, -1);
2462 }
2463
2464 Wayland_data_offer_destroy(data_device->drag_offer);
2465 data_device->drag_offer = NULL;
2466}
2467
2468static void notifyFromMimes(struct wl_list *mimes)
2469{
2470 int nformats = 0;
2471 char **new_mime_types = NULL;
2472 if (mimes) {
2473 nformats = WAYLAND_wl_list_length(mimes);
2474 size_t alloc_size = (nformats + 1) * sizeof(char *);
2475
2476 /* do a first pass to compute allocation size */
2477 SDL_MimeDataList *item = NULL;
2478 wl_list_for_each(item, mimes, link) {
2479 alloc_size += SDL_strlen(item->mime_type) + 1;
2480 }
2481
2482 new_mime_types = SDL_AllocateTemporaryMemory(alloc_size);
2483 if (!new_mime_types) {
2484 SDL_LogError(SDL_LOG_CATEGORY_INPUT, "unable to allocate new_mime_types");
2485 return;
2486 }
2487
2488 /* second pass to fill*/
2489 char *strPtr = (char *)(new_mime_types + nformats + 1);
2490 item = NULL;
2491 int i = 0;
2492 wl_list_for_each(item, mimes, link) {
2493 new_mime_types[i] = strPtr;
2494 strPtr = stpcpy(strPtr, item->mime_type) + 1;
2495 i++;
2496 }
2497 new_mime_types[nformats] = NULL;
2498 }
2499
2500 SDL_SendClipboardUpdate(false, new_mime_types, nformats);
2501}
2502
2503static void data_device_handle_selection(void *data, struct wl_data_device *wl_data_device,
2504 struct wl_data_offer *id)
2505{
2506 SDL_WaylandDataDevice *data_device = data;
2507 SDL_WaylandDataOffer *offer = NULL;
2508
2509 if (id) {
2510 offer = wl_data_offer_get_user_data(id);
2511 }
2512
2513 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2514 ". In data_device_listener . data_device_handle_selection on data_offer 0x%08x",
2515 (id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)id) : -1));
2516 if (data_device->selection_offer != offer) {
2517 Wayland_data_offer_destroy(data_device->selection_offer);
2518 data_device->selection_offer = offer;
2519 }
2520
2521 notifyFromMimes(offer ? &offer->mimes : NULL);
2522}
2523
2524static const struct wl_data_device_listener data_device_listener = {
2525 data_device_handle_data_offer,
2526 data_device_handle_enter,
2527 data_device_handle_leave,
2528 data_device_handle_motion,
2529 data_device_handle_drop,
2530 data_device_handle_selection
2531};
2532
2533static void primary_selection_device_handle_offer(void *data, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
2534 struct zwp_primary_selection_offer_v1 *id)
2535{
2536 SDL_WaylandPrimarySelectionOffer *primary_selection_offer = SDL_calloc(1, sizeof(*primary_selection_offer));
2537 if (primary_selection_offer) {
2538 primary_selection_offer->offer = id;
2539 primary_selection_offer->primary_selection_device = data;
2540 WAYLAND_wl_list_init(&(primary_selection_offer->mimes));
2541 zwp_primary_selection_offer_v1_set_user_data(id, primary_selection_offer);
2542 zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, primary_selection_offer);
2543 }
2544 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2545 ". In zwp_primary_selection_device_v1_listener . primary_selection_device_handle_offer on primary_selection_offer 0x%08x",
2546 (id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)id) : -1));
2547}
2548
2549static void primary_selection_device_handle_selection(void *data, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
2550 struct zwp_primary_selection_offer_v1 *id)
2551{
2552 SDL_WaylandPrimarySelectionDevice *primary_selection_device = data;
2553 SDL_WaylandPrimarySelectionOffer *offer = NULL;
2554
2555 if (id) {
2556 offer = zwp_primary_selection_offer_v1_get_user_data(id);
2557 }
2558
2559 if (primary_selection_device->selection_offer != offer) {
2560 Wayland_primary_selection_offer_destroy(primary_selection_device->selection_offer);
2561 primary_selection_device->selection_offer = offer;
2562 }
2563 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2564 ". In zwp_primary_selection_device_v1_listener . primary_selection_device_handle_selection on primary_selection_offer 0x%08x",
2565 (id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)id) : -1));
2566}
2567
2568static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
2569 primary_selection_device_handle_offer,
2570 primary_selection_device_handle_selection
2571};
2572
2573static void text_input_enter(void *data,
2574 struct zwp_text_input_v3 *zwp_text_input_v3,
2575 struct wl_surface *surface)
2576{
2577 // No-op
2578}
2579
2580static void text_input_leave(void *data,
2581 struct zwp_text_input_v3 *zwp_text_input_v3,
2582 struct wl_surface *surface)
2583{
2584 // No-op
2585}
2586
2587static void text_input_preedit_string(void *data,
2588 struct zwp_text_input_v3 *zwp_text_input_v3,
2589 const char *text,
2590 int32_t cursor_begin,
2591 int32_t cursor_end)
2592{
2593 SDL_WaylandTextInput *text_input = data;
2594 text_input->has_preedit = true;
2595 if (text) {
2596 int cursor_begin_utf8 = cursor_begin >= 0 ? (int)SDL_utf8strnlen(text, cursor_begin) : -1;
2597 int cursor_end_utf8 = cursor_end >= 0 ? (int)SDL_utf8strnlen(text, cursor_end) : -1;
2598 int cursor_size_utf8;
2599 if (cursor_end_utf8 >= 0) {
2600 if (cursor_begin_utf8 >= 0) {
2601 cursor_size_utf8 = cursor_end_utf8 - cursor_begin_utf8;
2602 } else {
2603 cursor_size_utf8 = cursor_end_utf8;
2604 }
2605 } else {
2606 cursor_size_utf8 = -1;
2607 }
2608 SDL_SendEditingText(text, cursor_begin_utf8, cursor_size_utf8);
2609 } else {
2610 SDL_SendEditingText("", 0, 0);
2611 }
2612}
2613
2614static void text_input_commit_string(void *data,
2615 struct zwp_text_input_v3 *zwp_text_input_v3,
2616 const char *text)
2617{
2618 SDL_SendKeyboardText(text);
2619}
2620
2621static void text_input_delete_surrounding_text(void *data,
2622 struct zwp_text_input_v3 *zwp_text_input_v3,
2623 uint32_t before_length,
2624 uint32_t after_length)
2625{
2626 // FIXME: Do we care about this event?
2627}
2628
2629static void text_input_done(void *data,
2630 struct zwp_text_input_v3 *zwp_text_input_v3,
2631 uint32_t serial)
2632{
2633 SDL_WaylandTextInput *text_input = data;
2634 if (!text_input->has_preedit) {
2635 SDL_SendEditingText("", 0, 0);
2636 }
2637 text_input->has_preedit = false;
2638}
2639
2640static const struct zwp_text_input_v3_listener text_input_listener = {
2641 text_input_enter,
2642 text_input_leave,
2643 text_input_preedit_string,
2644 text_input_commit_string,
2645 text_input_delete_surrounding_text,
2646 text_input_done
2647};
2648
2649void Wayland_create_data_device(SDL_VideoData *d)
2650{
2651 SDL_WaylandDataDevice *data_device = NULL;
2652
2653 if (!d->input->seat) {
2654 // No seat yet, will be initialized later.
2655 return;
2656 }
2657
2658 data_device = SDL_calloc(1, sizeof(*data_device));
2659 if (!data_device) {
2660 return;
2661 }
2662
2663 data_device->data_device = wl_data_device_manager_get_data_device(
2664 d->data_device_manager, d->input->seat);
2665 data_device->video_data = d;
2666
2667 if (!data_device->data_device) {
2668 SDL_free(data_device);
2669 } else {
2670 wl_data_device_set_user_data(data_device->data_device, data_device);
2671 wl_data_device_add_listener(data_device->data_device,
2672 &data_device_listener, data_device);
2673 d->input->data_device = data_device;
2674 }
2675}
2676
2677void Wayland_create_primary_selection_device(SDL_VideoData *d)
2678{
2679 SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
2680
2681 if (!d->input->seat) {
2682 // No seat yet, will be initialized later.
2683 return;
2684 }
2685
2686 primary_selection_device = SDL_calloc(1, sizeof(*primary_selection_device));
2687 if (!primary_selection_device) {
2688 return;
2689 }
2690
2691 primary_selection_device->primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(
2692 d->primary_selection_device_manager, d->input->seat);
2693 primary_selection_device->video_data = d;
2694
2695 if (!primary_selection_device->primary_selection_device) {
2696 SDL_free(primary_selection_device);
2697 } else {
2698 zwp_primary_selection_device_v1_set_user_data(primary_selection_device->primary_selection_device,
2699 primary_selection_device);
2700 zwp_primary_selection_device_v1_add_listener(primary_selection_device->primary_selection_device,
2701 &primary_selection_device_listener, primary_selection_device);
2702 d->input->primary_selection_device = primary_selection_device;
2703 }
2704}
2705
2706static void Wayland_create_text_input(SDL_VideoData *d)
2707{
2708 SDL_WaylandTextInput *text_input = NULL;
2709
2710 if (!d->input->seat) {
2711 // No seat yet, will be initialized later.
2712 return;
2713 }
2714
2715 text_input = SDL_calloc(1, sizeof(*text_input));
2716 if (!text_input) {
2717 return;
2718 }
2719
2720 text_input->text_input = zwp_text_input_manager_v3_get_text_input(
2721 d->text_input_manager, d->input->seat);
2722
2723 if (!text_input->text_input) {
2724 SDL_free(text_input);
2725 } else {
2726 zwp_text_input_v3_set_user_data(text_input->text_input, text_input);
2727 zwp_text_input_v3_add_listener(text_input->text_input,
2728 &text_input_listener, text_input);
2729 d->input->text_input = text_input;
2730 }
2731}
2732
2733void Wayland_create_text_input_manager(SDL_VideoData *d, uint32_t id)
2734{
2735#ifdef HAVE_FCITX
2736 const char *im_module = SDL_getenv("SDL_IM_MODULE");
2737 if (im_module && SDL_strcmp(im_module, "fcitx") == 0) {
2738 /* Override the Wayland text-input protocol when Fcitx is enabled, like how GTK_IM_MODULE does.
2739 *
2740 * The Fcitx wiki discourages enabling it under Wayland via SDL_IM_MODULE, so its presence must
2741 * be intentional, and this workaround is needed for fixing key repeat detection.
2742 */
2743 return;
2744 }
2745#endif
2746
2747 d->text_input_manager = wl_registry_bind(d->registry, id, &zwp_text_input_manager_v3_interface, 1);
2748 Wayland_create_text_input(d);
2749}
2750
2751// Pen/Tablet support...
2752
2753typedef struct SDL_WaylandPenTool // a stylus, etc, on a tablet.
2754{
2755 SDL_PenID instance_id;
2756 SDL_PenInfo info;
2757 SDL_Window *tool_focus;
2758 struct zwp_tablet_tool_v2 *wltool;
2759 float x;
2760 float y;
2761 bool frame_motion_set;
2762 float frame_axes[SDL_PEN_AXIS_COUNT];
2763 Uint32 frame_axes_set;
2764 int frame_pen_down;
2765 int frame_buttons[3];
2766} SDL_WaylandPenTool;
2767
2768static void tablet_tool_handle_type(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t type)
2769{
2770 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2771 switch (type) {
2772 #define CASE(typ) case ZWP_TABLET_TOOL_V2_TYPE_##typ: sdltool->info.subtype = SDL_PEN_TYPE_##typ; return
2773 CASE(ERASER);
2774 CASE(PEN);
2775 CASE(PENCIL);
2776 CASE(AIRBRUSH);
2777 CASE(BRUSH);
2778 #undef CASE
2779 default: sdltool->info.subtype = SDL_PEN_TYPE_UNKNOWN; // we'll decline to add this when the `done` event comes through.
2780 }
2781}
2782
2783static void tablet_tool_handle_hardware_serial(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial_hi, uint32_t serial_lo)
2784{
2785 // don't care about this atm.
2786}
2787
2788static void tablet_tool_handle_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t id_hi, uint32_t id_lo)
2789{
2790 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2791 sdltool->info.wacom_id = id_lo;
2792}
2793
2794static void tablet_tool_handle_capability(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t capability)
2795{
2796 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2797 switch (capability) {
2798 #define CASE(wltyp,sdltyp) case ZWP_TABLET_TOOL_V2_CAPABILITY_##wltyp: sdltool->info.capabilities |= sdltyp; return
2799 CASE(TILT, SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT);
2800 CASE(PRESSURE, SDL_PEN_CAPABILITY_PRESSURE);
2801 CASE(DISTANCE, SDL_PEN_CAPABILITY_DISTANCE);
2802 CASE(ROTATION, SDL_PEN_CAPABILITY_ROTATION);
2803 CASE(SLIDER, SDL_PEN_CAPABILITY_SLIDER);
2804 #undef CASE
2805 default: break; // unsupported here.
2806 }
2807}
2808
2809static void tablet_tool_handle_done(void *data, struct zwp_tablet_tool_v2 *tool)
2810{
2811}
2812
2813static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *tool)
2814{
2815 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2816 if (sdltool->instance_id) {
2817 SDL_RemovePenDevice(0, sdltool->instance_id);
2818 }
2819 zwp_tablet_tool_v2_destroy(tool);
2820 SDL_free(sdltool);
2821}
2822
2823static void tablet_tool_handle_proximity_in(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface)
2824{
2825 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2826 SDL_WindowData *windowdata = surface ? Wayland_GetWindowDataForOwnedSurface(surface) : NULL;
2827 sdltool->tool_focus = windowdata ? windowdata->sdlwindow : NULL;
2828
2829 SDL_assert(sdltool->instance_id == 0); // shouldn't be added at this point.
2830 if (sdltool->info.subtype != SDL_PEN_TYPE_UNKNOWN) { // don't tell SDL about it if we don't know its role.
2831 sdltool->instance_id = SDL_AddPenDevice(0, NULL, &sdltool->info, sdltool);
2832 }
2833
2834 // According to the docs, this should be followed by a motion event, where we'll send our SDL events.
2835}
2836
2837static void tablet_tool_handle_proximity_out(void *data, struct zwp_tablet_tool_v2 *tool)
2838{
2839 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2840 sdltool->tool_focus = NULL;
2841
2842 if (sdltool->instance_id) {
2843 SDL_RemovePenDevice(0, sdltool->instance_id);
2844 sdltool->instance_id = 0;
2845 }
2846}
2847
2848static void tablet_tool_handle_down(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial)
2849{
2850 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2851 sdltool->frame_pen_down = 1;
2852}
2853
2854static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 *tool)
2855{
2856 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2857 sdltool->frame_pen_down = 0;
2858}
2859
2860static void tablet_tool_handle_motion(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t sx_w, wl_fixed_t sy_w)
2861{
2862 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2863 SDL_Window *window = sdltool->tool_focus;
2864 if (window) {
2865 const SDL_WindowData *windowdata = window->internal;
2866 sdltool->x = (float)(wl_fixed_to_double(sx_w) * windowdata->pointer_scale.x);
2867 sdltool->y = (float)(wl_fixed_to_double(sy_w) * windowdata->pointer_scale.y);
2868 sdltool->frame_motion_set = true;
2869 }
2870}
2871
2872static void tablet_tool_handle_pressure(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t pressure)
2873{
2874 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2875 sdltool->frame_axes[SDL_PEN_AXIS_PRESSURE] = ((float) pressure) / 65535.0f;
2876 sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_PRESSURE);
2877 if (pressure) {
2878 sdltool->frame_axes[SDL_PEN_AXIS_DISTANCE] = 0.0f;
2879 sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_DISTANCE);
2880 }
2881}
2882
2883static void tablet_tool_handle_distance(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t distance)
2884{
2885 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2886 sdltool->frame_axes[SDL_PEN_AXIS_DISTANCE] = ((float) distance) / 65535.0f;
2887 sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_DISTANCE);
2888 if (distance) {
2889 sdltool->frame_axes[SDL_PEN_AXIS_PRESSURE] = 0.0f;
2890 sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_PRESSURE);
2891 }
2892}
2893
2894static void tablet_tool_handle_tilt(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t xtilt, wl_fixed_t ytilt)
2895{
2896 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2897 sdltool->frame_axes[SDL_PEN_AXIS_XTILT] = (float)(wl_fixed_to_double(xtilt));
2898 sdltool->frame_axes[SDL_PEN_AXIS_YTILT] = (float)(wl_fixed_to_double(ytilt));
2899 sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_XTILT) | (1u << SDL_PEN_AXIS_YTILT);
2900}
2901
2902static void tablet_tool_handle_button(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, uint32_t button, uint32_t state)
2903{
2904 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2905 int sdlbutton;
2906
2907 switch (button) {
2908 // see %{_includedir}/linux/input-event-codes.h
2909 case 0x14b: // BTN_STYLUS
2910 sdlbutton = 1;
2911 break;
2912 case 0x14c: // BTN_STYLUS2
2913 sdlbutton = 2;
2914 break;
2915 case 0x149: // BTN_STYLUS3
2916 sdlbutton = 3;
2917 break;
2918 default:
2919 return; // don't care about this button, I guess.
2920 }
2921
2922 SDL_assert((sdlbutton >= 1) && (sdlbutton <= SDL_arraysize(sdltool->frame_buttons)));
2923 sdltool->frame_buttons[sdlbutton-1] = (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED) ? 1 : 0;
2924}
2925
2926static void tablet_tool_handle_rotation(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t degrees)
2927{
2928 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2929 const float rotation = (float)(wl_fixed_to_double(degrees));
2930 sdltool->frame_axes[SDL_PEN_AXIS_ROTATION] = (rotation > 180.0f) ? (rotation - 360.0f) : rotation; // map to -180.0f ... 179.0f range
2931 sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_ROTATION);
2932}
2933
2934static void tablet_tool_handle_slider(void *data, struct zwp_tablet_tool_v2 *tool, int32_t position)
2935{
2936 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2937 sdltool->frame_axes[SDL_PEN_AXIS_SLIDER] = position / 65535.f;
2938 sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_SLIDER);
2939}
2940
2941static void tablet_tool_handle_wheel(void *data, struct zwp_tablet_tool_v2 *tool, int32_t degrees, int32_t clicks)
2942{
2943 // not supported at the moment
2944}
2945
2946static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t time)
2947{
2948 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
2949
2950 if (!sdltool->instance_id) {
2951 return; // Not a pen we report on.
2952 }
2953
2954 const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_MS_TO_NS(time));
2955 const SDL_PenID instance_id = sdltool->instance_id;
2956 SDL_Window *window = sdltool->tool_focus;
2957
2958 // I don't know if this is necessary (or makes sense), but send motion before pen downs, but after pen ups, so you don't get unexpected lines drawn.
2959 if (sdltool->frame_motion_set && (sdltool->frame_pen_down != -1)) {
2960 if (sdltool->frame_pen_down) {
2961 SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y);
2962 SDL_SendPenTouch(timestamp, instance_id, window, false, true); // !!! FIXME: how do we know what tip is in use?
2963 } else {
2964 SDL_SendPenTouch(timestamp, instance_id, window, false, false); // !!! FIXME: how do we know what tip is in use?
2965 SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y);
2966 }
2967 } else {
2968 if (sdltool->frame_pen_down != -1) {
2969 SDL_SendPenTouch(timestamp, instance_id, window, false, (sdltool->frame_pen_down != 0)); // !!! FIXME: how do we know what tip is in use?
2970 }
2971
2972 if (sdltool->frame_motion_set) {
2973 SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y);
2974 }
2975 }
2976
2977 for (SDL_PenAxis i = 0; i < SDL_PEN_AXIS_COUNT; i++) {
2978 if (sdltool->frame_axes_set & (1u << i)) {
2979 SDL_SendPenAxis(timestamp, instance_id, window, i, sdltool->frame_axes[i]);
2980 }
2981 }
2982
2983 for (int i = 0; i < SDL_arraysize(sdltool->frame_buttons); i++) {
2984 const int state = sdltool->frame_buttons[i];
2985 if (state != -1) {
2986 SDL_SendPenButton(timestamp, instance_id, window, (Uint8)(i + 1), (state != 0));
2987 sdltool->frame_buttons[i] = -1;
2988 }
2989 }
2990
2991 // reset for next frame.
2992 sdltool->frame_pen_down = -1;
2993 sdltool->frame_motion_set = false;
2994 sdltool->frame_axes_set = 0;
2995}
2996
2997static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = {
2998 tablet_tool_handle_type,
2999 tablet_tool_handle_hardware_serial,
3000 tablet_tool_handle_hardware_id_wacom,
3001 tablet_tool_handle_capability,
3002 tablet_tool_handle_done,
3003 tablet_tool_handle_removed,
3004 tablet_tool_handle_proximity_in,
3005 tablet_tool_handle_proximity_out,
3006 tablet_tool_handle_down,
3007 tablet_tool_handle_up,
3008 tablet_tool_handle_motion,
3009 tablet_tool_handle_pressure,
3010 tablet_tool_handle_distance,
3011 tablet_tool_handle_tilt,
3012 tablet_tool_handle_rotation,
3013 tablet_tool_handle_slider,
3014 tablet_tool_handle_wheel,
3015 tablet_tool_handle_button,
3016 tablet_tool_handle_frame
3017};
3018
3019
3020static void tablet_seat_handle_tablet_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_v2 *tablet)
3021{
3022 // don't care atm.
3023}
3024
3025static void tablet_seat_handle_tool_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_tool_v2 *tool)
3026{
3027 SDL_WaylandPenTool *sdltool = SDL_calloc(1, sizeof(*sdltool));
3028
3029 if (sdltool) { // if allocation failed, oh well, we won't report this device.
3030 sdltool->wltool = tool;
3031 sdltool->info.max_tilt = -1.0f;
3032 sdltool->info.num_buttons = -1;
3033 sdltool->frame_pen_down = -1;
3034 for (int i = 0; i < SDL_arraysize(sdltool->frame_buttons); i++) {
3035 sdltool->frame_buttons[i] = -1;
3036 }
3037
3038 // this will send a bunch of zwp_tablet_tool_v2 events right up front to tell
3039 // us device details, with a "done" event to let us know we have everything.
3040 zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, sdltool);
3041 }
3042}
3043
3044static void tablet_seat_handle_pad_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_pad_v2 *pad)
3045{
3046 // we don't care atm.
3047}
3048
3049static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
3050 tablet_seat_handle_tablet_added,
3051 tablet_seat_handle_tool_added,
3052 tablet_seat_handle_pad_added
3053};
3054
3055void Wayland_input_init_tablet_support(struct SDL_WaylandInput *input, struct zwp_tablet_manager_v2 *tablet_manager)
3056{
3057 if (!tablet_manager || !input->seat) {
3058 return;
3059 }
3060
3061 SDL_WaylandTabletInput *tablet_input = SDL_calloc(1, sizeof(*tablet_input));
3062 if (!tablet_input) {
3063 return;
3064 }
3065
3066 tablet_input->input = input;
3067 tablet_input->seat = zwp_tablet_manager_v2_get_tablet_seat(tablet_manager, input->seat);
3068
3069 zwp_tablet_seat_v2_add_listener(tablet_input->seat, &tablet_seat_listener, tablet_input);
3070
3071 input->tablet_input = tablet_input;
3072}
3073
3074static void Wayland_remove_all_pens_callback(SDL_PenID instance_id, void *handle, void *userdata)
3075{
3076 SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) handle;
3077 zwp_tablet_tool_v2_destroy(sdltool->wltool);
3078 SDL_free(sdltool);
3079}
3080
3081void Wayland_input_quit_tablet_support(struct SDL_WaylandInput *input)
3082{
3083 SDL_RemoveAllPenDevices(Wayland_remove_all_pens_callback, NULL);
3084
3085 if (input && input->tablet_input) {
3086 zwp_tablet_seat_v2_destroy(input->tablet_input->seat);
3087 SDL_free(input->tablet_input);
3088 input->tablet_input = NULL;
3089 }
3090}
3091
3092void Wayland_input_initialize_seat(SDL_VideoData *d)
3093{
3094 struct SDL_WaylandInput *input = d->input;
3095
3096 WAYLAND_wl_list_init(&touch_points);
3097
3098 if (d->data_device_manager) {
3099 Wayland_create_data_device(d);
3100 }
3101 if (d->primary_selection_device_manager) {
3102 Wayland_create_primary_selection_device(d);
3103 }
3104 if (d->text_input_manager) {
3105 Wayland_create_text_input(d);
3106 }
3107
3108 wl_seat_add_listener(input->seat, &seat_listener, input);
3109 wl_seat_set_user_data(input->seat, input);
3110
3111 if (d->tablet_manager) {
3112 Wayland_input_init_tablet_support(d->input, d->tablet_manager);
3113 }
3114
3115 WAYLAND_wl_display_flush(d->display);
3116}
3117
3118void Wayland_display_destroy_input(SDL_VideoData *d)
3119{
3120 struct SDL_WaylandInput *input = d->input;
3121
3122 if (input->keyboard_timestamps) {
3123 zwp_input_timestamps_v1_destroy(input->keyboard_timestamps);
3124 }
3125 if (input->pointer_timestamps) {
3126 zwp_input_timestamps_v1_destroy(input->pointer_timestamps);
3127 }
3128 if (input->touch_timestamps) {
3129 zwp_input_timestamps_v1_destroy(input->touch_timestamps);
3130 }
3131
3132 if (input->data_device) {
3133 Wayland_data_device_clear_selection(input->data_device);
3134 if (input->data_device->selection_offer) {
3135 Wayland_data_offer_destroy(input->data_device->selection_offer);
3136 }
3137 if (input->data_device->drag_offer) {
3138 Wayland_data_offer_destroy(input->data_device->drag_offer);
3139 }
3140 if (input->data_device->data_device) {
3141 if (wl_data_device_get_version(input->data_device->data_device) >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION) {
3142 wl_data_device_release(input->data_device->data_device);
3143 } else {
3144 wl_data_device_destroy(input->data_device->data_device);
3145 }
3146 }
3147 SDL_free(input->data_device);
3148 }
3149
3150 if (input->primary_selection_device) {
3151 if (input->primary_selection_device->selection_offer) {
3152 Wayland_primary_selection_offer_destroy(input->primary_selection_device->selection_offer);
3153 }
3154 if (input->primary_selection_device->selection_source) {
3155 Wayland_primary_selection_source_destroy(input->primary_selection_device->selection_source);
3156 }
3157 if (input->primary_selection_device->primary_selection_device) {
3158 zwp_primary_selection_device_v1_destroy(input->primary_selection_device->primary_selection_device);
3159 }
3160 SDL_free(input->primary_selection_device);
3161 }
3162
3163 if (input->text_input) {
3164 zwp_text_input_v3_destroy(input->text_input->text_input);
3165 SDL_free(input->text_input);
3166 }
3167
3168 if (input->keyboard) {
3169 if (wl_keyboard_get_version(input->keyboard) >= WL_KEYBOARD_RELEASE_SINCE_VERSION) {
3170 wl_keyboard_release(input->keyboard);
3171 } else {
3172 wl_keyboard_destroy(input->keyboard);
3173 }
3174 }
3175
3176 if (input->relative_pointer) {
3177 zwp_relative_pointer_v1_destroy(input->relative_pointer);
3178 }
3179
3180 if (input->cursor_shape) {
3181 wp_cursor_shape_device_v1_destroy(input->cursor_shape);
3182 }
3183
3184 if (input->pointer) {
3185 if (wl_pointer_get_version(input->pointer) >= WL_POINTER_RELEASE_SINCE_VERSION) {
3186 wl_pointer_release(input->pointer);
3187 } else {
3188 wl_pointer_destroy(input->pointer);
3189 }
3190 }
3191
3192 if (input->touch) {
3193 struct SDL_WaylandTouchPoint *tp, *tmp;
3194
3195 SDL_DelTouch(1);
3196 if (wl_touch_get_version(input->touch) >= WL_TOUCH_RELEASE_SINCE_VERSION) {
3197 wl_touch_release(input->touch);
3198 } else {
3199 wl_touch_destroy(input->touch);
3200 }
3201
3202 wl_list_for_each_safe (tp, tmp, &touch_points, link) {
3203 WAYLAND_wl_list_remove(&tp->link);
3204 SDL_free(tp);
3205 }
3206 }
3207
3208 if (input->tablet_input) {
3209 Wayland_input_quit_tablet_support(input);
3210 }
3211
3212 if (input->seat) {
3213 if (wl_seat_get_version(input->seat) >= WL_SEAT_RELEASE_SINCE_VERSION) {
3214 wl_seat_release(input->seat);
3215 } else {
3216 wl_seat_destroy(input->seat);
3217 }
3218 }
3219
3220 if (input->xkb.compose_state) {
3221 WAYLAND_xkb_compose_state_unref(input->xkb.compose_state);
3222 }
3223
3224 if (input->xkb.compose_table) {
3225 WAYLAND_xkb_compose_table_unref(input->xkb.compose_table);
3226 }
3227
3228 if (input->xkb.state) {
3229 WAYLAND_xkb_state_unref(input->xkb.state);
3230 }
3231
3232 if (input->xkb.keymap) {
3233 WAYLAND_xkb_keymap_unref(input->xkb.keymap);
3234 }
3235
3236 SDL_free(input);
3237 d->input = NULL;
3238}
3239
3240bool Wayland_input_enable_relative_pointer(struct SDL_WaylandInput *input)
3241{
3242 SDL_VideoDevice *vd = SDL_GetVideoDevice();
3243 SDL_VideoData *d = input->display;
3244 SDL_Window *window;
3245
3246 if (!d->relative_pointer_manager) {
3247 return false;
3248 }
3249
3250 if (!d->pointer_constraints) {
3251 return false;
3252 }
3253
3254 if (!input->pointer) {
3255 return false;
3256 }
3257
3258 /* If we have a pointer confine active, we must destroy it here because
3259 * creating a locked pointer otherwise would be a protocol error.
3260 */
3261 for (window = vd->windows; window; window = window->next) {
3262 pointer_confine_destroy(window);
3263 }
3264
3265 for (window = vd->windows; window; window = window->next) {
3266 Wayland_input_lock_pointer(input, window);
3267 }
3268
3269 d->relative_mouse_mode = 1;
3270
3271 return true;
3272}
3273
3274bool Wayland_input_disable_relative_pointer(struct SDL_WaylandInput *input)
3275{
3276 SDL_VideoDevice *vd = SDL_GetVideoDevice();
3277 SDL_VideoData *d = input->display;
3278 SDL_Window *window;
3279
3280 for (window = vd->windows; window; window = window->next) {
3281 Wayland_input_unlock_pointer(input, window);
3282 }
3283
3284 d->relative_mouse_mode = 0;
3285
3286 for (window = vd->windows; window; window = window->next) {
3287 Wayland_input_confine_pointer(input, window);
3288 }
3289
3290 return true;
3291}
3292
3293bool Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *window)
3294{
3295 SDL_WindowData *w = window->internal;
3296 SDL_VideoData *d = input->display;
3297 struct wl_region *confine_rect;
3298
3299 if (!d->pointer_constraints) {
3300 return SDL_SetError("Failed to confine pointer: compositor lacks support for the required zwp_pointer_constraints_v1 protocol");
3301 }
3302
3303 if (!input->pointer) {
3304 return SDL_SetError("No pointer to confine");
3305 }
3306
3307 /* A confine may already be active, in which case we should destroy it and
3308 * create a new one.
3309 */
3310 pointer_confine_destroy(window);
3311
3312 /* We cannot create a confine if the pointer is already locked. Defer until
3313 * the pointer is unlocked.
3314 */
3315 if (d->relative_mouse_mode) {
3316 return true;
3317 }
3318
3319 // Don't confine the pointer if it shouldn't be confined.
3320 if (SDL_RectEmpty(&window->mouse_rect) && !(window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
3321 return true;
3322 }
3323
3324 if (SDL_RectEmpty(&window->mouse_rect)) {
3325 confine_rect = NULL;
3326 } else {
3327 SDL_Rect scaled_mouse_rect;
3328
3329 scaled_mouse_rect.x = (int)SDL_floor(window->mouse_rect.x / w->pointer_scale.x);
3330 scaled_mouse_rect.y = (int)SDL_floor(window->mouse_rect.y / w->pointer_scale.y);
3331 scaled_mouse_rect.w = (int)SDL_ceil(window->mouse_rect.w / w->pointer_scale.x);
3332 scaled_mouse_rect.h = (int)SDL_ceil(window->mouse_rect.h / w->pointer_scale.y);
3333
3334 confine_rect = wl_compositor_create_region(d->compositor);
3335 wl_region_add(confine_rect,
3336 scaled_mouse_rect.x,
3337 scaled_mouse_rect.y,
3338 scaled_mouse_rect.w,
3339 scaled_mouse_rect.h);
3340 }
3341
3342 struct zwp_confined_pointer_v1 *confined_pointer =
3343 zwp_pointer_constraints_v1_confine_pointer(d->pointer_constraints,
3344 w->surface,
3345 input->pointer,
3346 confine_rect,
3347 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
3348 zwp_confined_pointer_v1_add_listener(confined_pointer,
3349 &confined_pointer_listener,
3350 window);
3351
3352 if (confine_rect) {
3353 wl_region_destroy(confine_rect);
3354 }
3355
3356 w->confined_pointer = confined_pointer;
3357 return true;
3358}
3359
3360bool Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input, SDL_Window *window)
3361{
3362 pointer_confine_destroy(window);
3363 return true;
3364}
3365
3366bool Wayland_input_grab_keyboard(SDL_Window *window, struct SDL_WaylandInput *input)
3367{
3368 SDL_WindowData *w = window->internal;
3369 SDL_VideoData *d = input->display;
3370
3371 if (!d->key_inhibitor_manager) {
3372 return SDL_SetError("Failed to grab keyboard: compositor lacks support for the required zwp_keyboard_shortcuts_inhibit_manager_v1 protocol");
3373 }
3374
3375 if (w->key_inhibitor) {
3376 return true;
3377 }
3378
3379 w->key_inhibitor =
3380 zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(d->key_inhibitor_manager,
3381 w->surface,
3382 input->seat);
3383
3384 return true;
3385}
3386
3387bool Wayland_input_ungrab_keyboard(SDL_Window *window)
3388{
3389 SDL_WindowData *w = window->internal;
3390
3391 if (w->key_inhibitor) {
3392 zwp_keyboard_shortcuts_inhibitor_v1_destroy(w->key_inhibitor);
3393 w->key_inhibitor = NULL;
3394 }
3395
3396 return true;
3397}
3398
3399void Wayland_UpdateImplicitGrabSerial(struct SDL_WaylandInput *input, Uint32 serial)
3400{
3401 if (serial > input->last_implicit_grab_serial) {
3402 input->last_implicit_grab_serial = serial;
3403 Wayland_data_device_set_serial(input->data_device, serial);
3404 Wayland_primary_selection_device_set_serial(input->primary_selection_device, serial);
3405 }
3406}
3407
3408#endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandevents_c.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandevents_c.h
new file mode 100644
index 0000000..6158882
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandevents_c.h
@@ -0,0 +1,195 @@
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#ifndef SDL_waylandevents_h_
25#define SDL_waylandevents_h_
26
27#include "../../events/SDL_mouse_c.h"
28#include "../../events/SDL_pen_c.h"
29
30#include "SDL_waylandvideo.h"
31#include "SDL_waylandwindow.h"
32#include "SDL_waylanddatamanager.h"
33#include "SDL_waylandkeyboard.h"
34
35enum SDL_WaylandAxisEvent
36{
37 AXIS_EVENT_CONTINUOUS = 0,
38 AXIS_EVENT_DISCRETE,
39 AXIS_EVENT_VALUE120
40};
41
42struct SDL_WaylandTabletSeat;
43
44typedef struct SDL_WaylandTabletInput
45{
46 struct SDL_WaylandInput *input;
47 struct zwp_tablet_seat_v2 *seat;
48} SDL_WaylandTabletInput;
49
50typedef struct
51{
52 int32_t repeat_rate; // Repeat rate in range of [1, 1000] character(s) per second
53 int32_t repeat_delay_ms; // Time to first repeat event in milliseconds
54 Uint32 keyboard_id; // ID of the source keyboard.
55 bool is_initialized;
56
57 bool is_key_down;
58 uint32_t key;
59 Uint64 wl_press_time_ns; // Key press time as reported by the Wayland API
60 Uint64 sdl_press_time_ns; // Key press time expressed in SDL ticks
61 Uint64 next_repeat_ns; // Next repeat event in nanoseconds
62 uint32_t scancode;
63 char text[8];
64} SDL_WaylandKeyboardRepeat;
65
66struct SDL_WaylandInput
67{
68 SDL_VideoData *display;
69 struct wl_seat *seat;
70 struct wl_pointer *pointer;
71 struct wl_touch *touch;
72 struct wl_keyboard *keyboard;
73 SDL_WaylandDataDevice *data_device;
74 SDL_WaylandPrimarySelectionDevice *primary_selection_device;
75 SDL_WaylandTextInput *text_input;
76 struct wp_cursor_shape_device_v1 *cursor_shape;
77 struct zwp_relative_pointer_v1 *relative_pointer;
78 struct zwp_input_timestamps_v1 *keyboard_timestamps;
79 struct zwp_input_timestamps_v1 *pointer_timestamps;
80 struct zwp_input_timestamps_v1 *touch_timestamps;
81 SDL_WindowData *pointer_focus;
82 SDL_WindowData *keyboard_focus;
83 SDL_CursorData *current_cursor;
84 SDL_KeyboardID keyboard_id;
85 SDL_MouseID pointer_id;
86 uint32_t pointer_enter_serial;
87
88 // High-resolution event timestamps
89 Uint64 keyboard_timestamp_ns;
90 Uint64 pointer_timestamp_ns;
91 Uint64 touch_timestamp_ns;
92
93 // Last motion location
94 wl_fixed_t sx_w;
95 wl_fixed_t sy_w;
96
97 SDL_MouseButtonFlags buttons_pressed;
98
99 // The serial of the last implicit grab event for window activation and selection data.
100 Uint32 last_implicit_grab_serial;
101
102 struct
103 {
104 struct xkb_keymap *keymap;
105 struct xkb_state *state;
106 struct xkb_compose_table *compose_table;
107 struct xkb_compose_state *compose_state;
108
109 // Keyboard layout "group"
110 uint32_t current_group;
111
112 // Modifier bitshift values
113 uint32_t idx_shift;
114 uint32_t idx_ctrl;
115 uint32_t idx_alt;
116 uint32_t idx_gui;
117 uint32_t idx_mod3;
118 uint32_t idx_mod5;
119 uint32_t idx_num;
120 uint32_t idx_caps;
121
122 // Current system modifier flags
123 uint32_t wl_pressed_modifiers;
124 uint32_t wl_locked_modifiers;
125 } xkb;
126
127 // information about axis events on current frame
128 struct
129 {
130 enum SDL_WaylandAxisEvent x_axis_type;
131 float x;
132
133 enum SDL_WaylandAxisEvent y_axis_type;
134 float y;
135
136 // Event timestamp in nanoseconds
137 Uint64 timestamp_ns;
138 SDL_MouseWheelDirection direction;
139 } pointer_curr_axis_info;
140
141 SDL_WaylandKeyboardRepeat keyboard_repeat;
142
143 SDL_WaylandTabletInput *tablet_input;
144
145 bool keyboard_is_virtual;
146
147 // Current SDL modifier flags
148 SDL_Keymod pressed_modifiers;
149 SDL_Keymod locked_modifiers;
150};
151
152
153extern Uint64 Wayland_GetTouchTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms);
154
155extern void Wayland_PumpEvents(SDL_VideoDevice *_this);
156extern void Wayland_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window);
157extern int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS);
158
159extern void Wayland_create_data_device(SDL_VideoData *d);
160extern void Wayland_create_primary_selection_device(SDL_VideoData *d);
161
162extern void Wayland_create_text_input_manager(SDL_VideoData *d, uint32_t id);
163
164extern void Wayland_input_initialize_seat(SDL_VideoData *d);
165extern void Wayland_display_destroy_input(SDL_VideoData *d);
166
167extern void Wayland_input_init_relative_pointer(SDL_VideoData *d);
168extern bool Wayland_input_enable_relative_pointer(struct SDL_WaylandInput *input);
169extern bool Wayland_input_disable_relative_pointer(struct SDL_WaylandInput *input);
170
171extern bool Wayland_input_lock_pointer(struct SDL_WaylandInput *input, SDL_Window *window);
172extern bool Wayland_input_unlock_pointer(struct SDL_WaylandInput *input, SDL_Window *window);
173
174extern bool Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *window);
175extern bool Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input, SDL_Window *window);
176
177extern bool Wayland_input_grab_keyboard(SDL_Window *window, struct SDL_WaylandInput *input);
178extern bool Wayland_input_ungrab_keyboard(SDL_Window *window);
179
180extern void Wayland_input_init_tablet_support(struct SDL_WaylandInput *input, struct zwp_tablet_manager_v2 *tablet_manager);
181extern void Wayland_input_quit_tablet_support(struct SDL_WaylandInput *input);
182
183extern void Wayland_RegisterTimestampListeners(struct SDL_WaylandInput *input);
184extern void Wayland_CreateCursorShapeDevice(struct SDL_WaylandInput *input);
185
186/* The implicit grab serial needs to be updated on:
187 * - Keyboard key down/up
188 * - Mouse button down
189 * - Touch event down
190 * - Tablet tool down
191 * - Tablet tool button down/up
192 */
193extern void Wayland_UpdateImplicitGrabSerial(struct SDL_WaylandInput *input, Uint32 serial);
194
195#endif // SDL_waylandevents_h_
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandkeyboard.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandkeyboard.c
new file mode 100644
index 0000000..df1628c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandkeyboard.c
@@ -0,0 +1,209 @@
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#include "SDL_internal.h"
22
23#if SDL_VIDEO_DRIVER_WAYLAND
24
25#include "../SDL_sysvideo.h"
26#include "SDL_waylandvideo.h"
27#include "SDL_waylandevents_c.h"
28#include "../../events/SDL_keyboard_c.h"
29#include "text-input-unstable-v3-client-protocol.h"
30
31bool Wayland_InitKeyboard(SDL_VideoDevice *_this)
32{
33#ifdef SDL_USE_IME
34 SDL_VideoData *internal = _this->internal;
35 if (!internal->text_input_manager) {
36 SDL_IME_Init();
37 }
38#endif
39 SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
40
41 return true;
42}
43
44void Wayland_QuitKeyboard(SDL_VideoDevice *_this)
45{
46#ifdef SDL_USE_IME
47 SDL_VideoData *internal = _this->internal;
48 if (!internal->text_input_manager) {
49 SDL_IME_Quit();
50 }
51#endif
52}
53
54bool Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
55{
56 SDL_VideoData *internal = _this->internal;
57 struct SDL_WaylandInput *input = internal->input;
58
59 if (internal->text_input_manager) {
60 if (input && input->text_input) {
61 const SDL_Rect *rect = &input->text_input->cursor_rect;
62 enum zwp_text_input_v3_content_hint hint = ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE;
63 enum zwp_text_input_v3_content_purpose purpose;
64
65 switch (SDL_GetTextInputType(props)) {
66 default:
67 case SDL_TEXTINPUT_TYPE_TEXT:
68 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
69 break;
70 case SDL_TEXTINPUT_TYPE_TEXT_NAME:
71 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME;
72 break;
73 case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
74 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL;
75 break;
76 case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
77 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
78 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
79 break;
80 case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
81 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
82 hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
83 break;
84 case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
85 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
86 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
87 break;
88 case SDL_TEXTINPUT_TYPE_NUMBER:
89 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER;
90 break;
91 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
92 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
93 hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
94 break;
95 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
96 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
97 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
98 break;
99 }
100
101 switch (SDL_GetTextInputCapitalization(props)) {
102 default:
103 case SDL_CAPITALIZE_NONE:
104 break;
105 case SDL_CAPITALIZE_LETTERS:
106 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE;
107 break;
108 case SDL_CAPITALIZE_WORDS:
109 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE;
110 break;
111 case SDL_CAPITALIZE_SENTENCES:
112 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION;
113 break;
114 }
115
116 if (SDL_GetTextInputAutocorrect(props)) {
117 hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK);
118 }
119 if (SDL_GetTextInputMultiline(props)) {
120 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE;
121 }
122
123 zwp_text_input_v3_enable(input->text_input->text_input);
124
125 // Now that it's enabled, set the input properties
126 zwp_text_input_v3_set_content_type(input->text_input->text_input, hint, purpose);
127 if (!SDL_RectEmpty(rect)) {
128 // This gets reset on enable so we have to cache it
129 zwp_text_input_v3_set_cursor_rectangle(input->text_input->text_input,
130 rect->x,
131 rect->y,
132 rect->w,
133 rect->h);
134 }
135 zwp_text_input_v3_commit(input->text_input->text_input);
136 }
137 }
138
139 if (input && input->xkb.compose_state) {
140 // Reset compose state so composite and dead keys don't carry over
141 WAYLAND_xkb_compose_state_reset(input->xkb.compose_state);
142 }
143
144 return Wayland_UpdateTextInputArea(_this, window);
145}
146
147bool Wayland_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
148{
149 SDL_VideoData *internal = _this->internal;
150 struct SDL_WaylandInput *input = internal->input;
151
152 if (internal->text_input_manager) {
153 if (input && input->text_input) {
154 zwp_text_input_v3_disable(input->text_input->text_input);
155 zwp_text_input_v3_commit(input->text_input->text_input);
156 }
157 }
158#ifdef SDL_USE_IME
159 else {
160 SDL_IME_Reset();
161 }
162#endif
163
164 if (input && input->xkb.compose_state) {
165 // Reset compose state so composite and dead keys don't carry over
166 WAYLAND_xkb_compose_state_reset(input->xkb.compose_state);
167 }
168 return true;
169}
170
171bool Wayland_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
172{
173 SDL_VideoData *internal = _this->internal;
174 if (internal->text_input_manager) {
175 struct SDL_WaylandInput *input = internal->input;
176 if (input && input->text_input) {
177 if (!SDL_RectsEqual(&window->text_input_rect, &input->text_input->cursor_rect)) {
178 SDL_copyp(&input->text_input->cursor_rect, &window->text_input_rect);
179 zwp_text_input_v3_set_cursor_rectangle(input->text_input->text_input,
180 window->text_input_rect.x,
181 window->text_input_rect.y,
182 window->text_input_rect.w,
183 window->text_input_rect.h);
184 zwp_text_input_v3_commit(input->text_input->text_input);
185 }
186 }
187 }
188
189#ifdef SDL_USE_IME
190 else {
191 SDL_IME_UpdateTextInputArea(window);
192 }
193#endif
194 return true;
195}
196
197bool Wayland_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
198{
199 /* In reality we just want to return true when the screen keyboard is the
200 * _only_ way to get text input. So, in addition to checking for the text
201 * input protocol, make sure we don't have any physical keyboards either.
202 */
203 SDL_VideoData *internal = _this->internal;
204 bool haskeyboard = (internal->input != NULL) && (internal->input->keyboard != NULL);
205 bool hastextmanager = (internal->text_input_manager != NULL);
206 return !haskeyboard && hastextmanager;
207}
208
209#endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandkeyboard.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandkeyboard.h
new file mode 100644
index 0000000..f570edb
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandkeyboard.h
@@ -0,0 +1,40 @@
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#include "SDL_internal.h"
22
23#ifndef SDL_waylandkeyboard_h_
24#define SDL_waylandkeyboard_h_
25
26typedef struct SDL_WaylandTextInput
27{
28 struct zwp_text_input_v3 *text_input;
29 SDL_Rect cursor_rect;
30 bool has_preedit;
31} SDL_WaylandTextInput;
32
33extern bool Wayland_InitKeyboard(SDL_VideoDevice *_this);
34extern void Wayland_QuitKeyboard(SDL_VideoDevice *_this);
35extern bool Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
36extern bool Wayland_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
37extern bool Wayland_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);
38extern bool Wayland_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
39
40#endif // SDL_waylandkeyboard_h_
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmessagebox.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmessagebox.c
new file mode 100644
index 0000000..3d6f1f8
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmessagebox.c
@@ -0,0 +1,196 @@
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_waylandmessagebox.h"
27
28#define ZENITY_VERSION_LEN 32 // Number of bytes to read from zenity --version (including NUL)
29
30#define MAX_BUTTONS 8 // Maximum number of buttons supported
31
32static bool parse_zenity_version(const char *version, int *major, int *minor)
33{
34 /* We expect the version string is in the form of MAJOR.MINOR.MICRO
35 * as described in meson.build. We'll ignore everything after that.
36 */
37 const char *version_ptr = version;
38 char *end_ptr = NULL;
39 int tmp = (int) SDL_strtol(version_ptr, &end_ptr, 10);
40 if (tmp == 0 && end_ptr == version_ptr) {
41 return SDL_SetError("failed to get zenity major version number");
42 }
43 *major = tmp;
44
45 if (*end_ptr == '.') {
46 version_ptr = end_ptr + 1; // skip the dot
47 tmp = (int) SDL_strtol(version_ptr, &end_ptr, 10);
48 if (tmp == 0 && end_ptr == version_ptr) {
49 return SDL_SetError("failed to get zenity minor version number");
50 }
51 *minor = tmp;
52 } else {
53 *minor = 0;
54 }
55 return true;
56}
57
58static bool get_zenity_version(int *major, int *minor)
59{
60 const char *argv[] = { "zenity", "--version", NULL };
61 bool result = false;
62
63 SDL_Process *process = SDL_CreateProcess(argv, true);
64 if (!process) {
65 return false;
66 }
67
68 char *output = SDL_ReadProcess(process, NULL, NULL);
69 if (output) {
70 result = parse_zenity_version(output, major, minor);
71 SDL_free(output);
72 }
73 SDL_DestroyProcess(process);
74
75 return result;
76}
77
78bool Wayland_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
79{
80 int zenity_major = 0, zenity_minor = 0, output_len = 0;
81 int argc = 5, i;
82 const char *argv[5 + 2 /* icon name */ + 2 /* title */ + 2 /* message */ + 2 * MAX_BUTTONS + 1 /* NULL */] = {
83 "zenity", "--question", "--switch", "--no-wrap", "--no-markup"
84 };
85 SDL_Process *process;
86
87 // Are we trying to connect to or are currently in a Wayland session?
88 if (!SDL_getenv("WAYLAND_DISPLAY")) {
89 const char *session = SDL_getenv("XDG_SESSION_TYPE");
90 if (session && SDL_strcasecmp(session, "wayland") != 0) {
91 return SDL_SetError("Not on a wayland display");
92 }
93 }
94
95 if (messageboxdata->numbuttons > MAX_BUTTONS) {
96 return SDL_SetError("Too many buttons (%d max allowed)", MAX_BUTTONS);
97 }
98
99 // get zenity version so we know which arg to use
100 if (!get_zenity_version(&zenity_major, &zenity_minor)) {
101 return false; // get_zenity_version() calls SDL_SetError(), so message is already set
102 }
103
104 /* https://gitlab.gnome.org/GNOME/zenity/-/commit/c686bdb1b45e95acf010efd9ca0c75527fbb4dea
105 * This commit removed --icon-name without adding a deprecation notice.
106 * We need to handle it gracefully, otherwise no message box will be shown.
107 */
108 argv[argc++] = zenity_major > 3 || (zenity_major == 3 && zenity_minor >= 90) ? "--icon" : "--icon-name";
109 switch (messageboxdata->flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) {
110 case SDL_MESSAGEBOX_ERROR:
111 argv[argc++] = "dialog-error";
112 break;
113 case SDL_MESSAGEBOX_WARNING:
114 argv[argc++] = "dialog-warning";
115 break;
116 case SDL_MESSAGEBOX_INFORMATION:
117 default:
118 argv[argc++] = "dialog-information";
119 break;
120 }
121
122 if (messageboxdata->title && messageboxdata->title[0]) {
123 argv[argc++] = "--title";
124 argv[argc++] = messageboxdata->title;
125 } else {
126 argv[argc++] = "--title=";
127 }
128
129 if (messageboxdata->message && messageboxdata->message[0]) {
130 argv[argc++] = "--text";
131 argv[argc++] = messageboxdata->message;
132 } else {
133 argv[argc++] = "--text=";
134 }
135
136 for (i = 0; i < messageboxdata->numbuttons; ++i) {
137 if (messageboxdata->buttons[i].text && messageboxdata->buttons[i].text[0]) {
138 int len = SDL_strlen(messageboxdata->buttons[i].text);
139 if (len > output_len) {
140 output_len = len;
141 }
142
143 argv[argc++] = "--extra-button";
144 argv[argc++] = messageboxdata->buttons[i].text;
145 } else {
146 argv[argc++] = "--extra-button=";
147 }
148 }
149 if (messageboxdata->numbuttons == 0) {
150 argv[argc++] = "--extra-button=OK";
151 }
152 argv[argc] = NULL;
153
154 SDL_PropertiesID props = SDL_CreateProperties();
155 if (!props) {
156 return false;
157 }
158 SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, argv);
159 // If buttonID is set we need to wait and read the results
160 if (buttonID) {
161 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP);
162 } else {
163 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_NULL);
164 }
165 process = SDL_CreateProcessWithProperties(props);
166 SDL_DestroyProperties(props);
167 if (!process) {
168 return false;
169 }
170 if (buttonID) {
171 char *output = SDL_ReadProcess(process, NULL, NULL);
172 if (output) {
173 // It likes to add a newline...
174 char *tmp = SDL_strrchr(output, '\n');
175 if (tmp) {
176 *tmp = '\0';
177 }
178
179 // Check which button got pressed
180 for (i = 0; i < messageboxdata->numbuttons; i += 1) {
181 if (messageboxdata->buttons[i].text) {
182 if (SDL_strcmp(output, messageboxdata->buttons[i].text) == 0) {
183 *buttonID = messageboxdata->buttons[i].buttonID;
184 break;
185 }
186 }
187 }
188 SDL_free(output);
189 }
190 }
191 SDL_DestroyProcess(process);
192
193 return true;
194}
195
196#endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmessagebox.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmessagebox.h
new file mode 100644
index 0000000..af3012f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmessagebox.h
@@ -0,0 +1,31 @@
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#ifndef SDL_waylandmessagebox_h_
23#define SDL_waylandmessagebox_h_
24
25#ifdef SDL_VIDEO_DRIVER_WAYLAND
26
27extern bool Wayland_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID);
28
29#endif // SDL_VIDEO_DRIVER_WAYLAND
30
31#endif // SDL_waylandmessagebox_h_
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmouse.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmouse.c
new file mode 100644
index 0000000..8075c3a
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmouse.c
@@ -0,0 +1,1057 @@
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_sysvideo.h"
27#include "../SDL_video_c.h"
28
29#include "../../events/SDL_mouse_c.h"
30#include "SDL_waylandvideo.h"
31#include "../SDL_pixels_c.h"
32#include "SDL_waylandevents_c.h"
33
34#include "wayland-cursor.h"
35#include "SDL_waylandmouse.h"
36#include "SDL_waylandshmbuffer.h"
37
38#include "cursor-shape-v1-client-protocol.h"
39#include "pointer-constraints-unstable-v1-client-protocol.h"
40#include "viewporter-client-protocol.h"
41
42#include "../../SDL_hints_c.h"
43
44static SDL_Cursor *sys_cursors[SDL_HITTEST_RESIZE_LEFT + 1];
45
46static bool Wayland_SetRelativeMouseMode(bool enabled);
47
48typedef struct
49{
50 struct Wayland_SHMBuffer shmBuffer;
51 double scale;
52 struct wl_list node;
53} Wayland_ScaledCustomCursor;
54
55typedef struct
56{
57 SDL_Surface *sdl_cursor_surface;
58 int hot_x;
59 int hot_y;
60 struct wl_list scaled_cursor_cache;
61} Wayland_CustomCursor;
62
63typedef struct
64{
65 struct wl_buffer *wl_buffer;
66 Uint32 duration;
67} Wayland_SystemCursorFrame;
68
69typedef struct
70{
71 Wayland_SystemCursorFrame *frames;
72 struct wl_callback *frame_callback;
73 Uint64 last_frame_callback_time_ms;
74 Uint64 current_frame_time_ms;
75 Uint32 total_duration;
76 int num_frames;
77 int current_frame;
78 SDL_SystemCursor id;
79} Wayland_SystemCursor;
80
81struct SDL_CursorData
82{
83 union
84 {
85 Wayland_CustomCursor custom;
86 Wayland_SystemCursor system;
87 } cursor_data;
88
89 struct wl_surface *surface;
90 struct wp_viewport *viewport;
91
92 bool is_system_cursor;
93};
94
95static int dbus_cursor_size;
96static char *dbus_cursor_theme;
97
98static void Wayland_FreeCursorThemes(SDL_VideoData *vdata)
99{
100 for (int i = 0; i < vdata->num_cursor_themes; i += 1) {
101 WAYLAND_wl_cursor_theme_destroy(vdata->cursor_themes[i].theme);
102 }
103 vdata->num_cursor_themes = 0;
104 SDL_free(vdata->cursor_themes);
105 vdata->cursor_themes = NULL;
106}
107
108#ifdef SDL_USE_LIBDBUS
109
110#include "../../core/linux/SDL_dbus.h"
111
112#define CURSOR_NODE "org.freedesktop.portal.Desktop"
113#define CURSOR_PATH "/org/freedesktop/portal/desktop"
114#define CURSOR_INTERFACE "org.freedesktop.portal.Settings"
115#define CURSOR_NAMESPACE "org.gnome.desktop.interface"
116#define CURSOR_SIGNAL_NAME "SettingChanged"
117#define CURSOR_SIZE_KEY "cursor-size"
118#define CURSOR_THEME_KEY "cursor-theme"
119
120static DBusMessage *Wayland_ReadDBusProperty(SDL_DBusContext *dbus, const char *key)
121{
122 static const char *iface = "org.gnome.desktop.interface";
123
124 DBusMessage *reply = NULL;
125 DBusMessage *msg = dbus->message_new_method_call(CURSOR_NODE,
126 CURSOR_PATH,
127 CURSOR_INTERFACE,
128 "Read"); // Method
129
130 if (msg) {
131 if (dbus->message_append_args(msg, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
132 reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, NULL);
133 }
134 dbus->message_unref(msg);
135 }
136
137 return reply;
138}
139
140static bool Wayland_ParseDBusReply(SDL_DBusContext *dbus, DBusMessage *reply, int type, void *value)
141{
142 DBusMessageIter iter[3];
143
144 dbus->message_iter_init(reply, &iter[0]);
145 if (dbus->message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) {
146 return false;
147 }
148
149 dbus->message_iter_recurse(&iter[0], &iter[1]);
150 if (dbus->message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) {
151 return false;
152 }
153
154 dbus->message_iter_recurse(&iter[1], &iter[2]);
155 if (dbus->message_iter_get_arg_type(&iter[2]) != type) {
156 return false;
157 }
158
159 dbus->message_iter_get_basic(&iter[2], value);
160
161 return true;
162}
163
164static DBusHandlerResult Wayland_DBusCursorMessageFilter(DBusConnection *conn, DBusMessage *msg, void *data)
165{
166 SDL_DBusContext *dbus = SDL_DBus_GetContext();
167 SDL_VideoData *vdata = (SDL_VideoData *)data;
168
169 if (dbus->message_is_signal(msg, CURSOR_INTERFACE, CURSOR_SIGNAL_NAME)) {
170 DBusMessageIter signal_iter, variant_iter;
171 const char *namespace, *key;
172
173 dbus->message_iter_init(msg, &signal_iter);
174 // Check if the parameters are what we expect
175 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING) {
176 goto not_our_signal;
177 }
178 dbus->message_iter_get_basic(&signal_iter, &namespace);
179 if (SDL_strcmp(CURSOR_NAMESPACE, namespace) != 0) {
180 goto not_our_signal;
181 }
182 if (!dbus->message_iter_next(&signal_iter)) {
183 goto not_our_signal;
184 }
185 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING) {
186 goto not_our_signal;
187 }
188 dbus->message_iter_get_basic(&signal_iter, &key);
189 if (SDL_strcmp(CURSOR_SIZE_KEY, key) == 0) {
190 int new_cursor_size;
191
192 if (!dbus->message_iter_next(&signal_iter)) {
193 goto not_our_signal;
194 }
195 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_VARIANT) {
196 goto not_our_signal;
197 }
198 dbus->message_iter_recurse(&signal_iter, &variant_iter);
199 if (dbus->message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_INT32) {
200 goto not_our_signal;
201 }
202 dbus->message_iter_get_basic(&variant_iter, &new_cursor_size);
203
204 if (dbus_cursor_size != new_cursor_size) {
205 dbus_cursor_size = new_cursor_size;
206 SDL_SetCursor(NULL); // Force cursor update
207 }
208 } else if (SDL_strcmp(CURSOR_THEME_KEY, key) == 0) {
209 const char *new_cursor_theme = NULL;
210
211 if (!dbus->message_iter_next(&signal_iter)) {
212 goto not_our_signal;
213 }
214 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_VARIANT) {
215 goto not_our_signal;
216 }
217 dbus->message_iter_recurse(&signal_iter, &variant_iter);
218 if (dbus->message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_STRING) {
219 goto not_our_signal;
220 }
221 dbus->message_iter_get_basic(&variant_iter, &new_cursor_theme);
222
223 if (!dbus_cursor_theme || !new_cursor_theme || SDL_strcmp(dbus_cursor_theme, new_cursor_theme) != 0) {
224 SDL_free(dbus_cursor_theme);
225 if (new_cursor_theme) {
226 dbus_cursor_theme = SDL_strdup(new_cursor_theme);
227 } else {
228 dbus_cursor_theme = NULL;
229 }
230
231 // Purge the current cached themes and force a cursor refresh.
232 Wayland_FreeCursorThemes(vdata);
233 SDL_SetCursor(NULL);
234 }
235 } else {
236 goto not_our_signal;
237 }
238
239 return DBUS_HANDLER_RESULT_HANDLED;
240 }
241
242not_our_signal:
243 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
244}
245
246static void Wayland_DBusInitCursorProperties(SDL_VideoData *vdata)
247{
248 DBusMessage *reply;
249 SDL_DBusContext *dbus = SDL_DBus_GetContext();
250 bool add_filter = false;
251
252 if (!dbus) {
253 return;
254 }
255
256 if ((reply = Wayland_ReadDBusProperty(dbus, CURSOR_SIZE_KEY))) {
257 if (Wayland_ParseDBusReply(dbus, reply, DBUS_TYPE_INT32, &dbus_cursor_size)) {
258 add_filter = true;
259 }
260 dbus->message_unref(reply);
261 }
262
263 if ((reply = Wayland_ReadDBusProperty(dbus, CURSOR_THEME_KEY))) {
264 const char *temp = NULL;
265 if (Wayland_ParseDBusReply(dbus, reply, DBUS_TYPE_STRING, &temp)) {
266 add_filter = true;
267
268 if (temp) {
269 dbus_cursor_theme = SDL_strdup(temp);
270 }
271 }
272 dbus->message_unref(reply);
273 }
274
275 // Only add the filter if at least one of the settings we want is present.
276 if (add_filter) {
277 dbus->bus_add_match(dbus->session_conn,
278 "type='signal', interface='" CURSOR_INTERFACE "',"
279 "member='" CURSOR_SIGNAL_NAME "', arg0='" CURSOR_NAMESPACE "'",
280 NULL);
281 dbus->connection_add_filter(dbus->session_conn, &Wayland_DBusCursorMessageFilter, vdata, NULL);
282 dbus->connection_flush(dbus->session_conn);
283 }
284}
285
286static void Wayland_DBusFinishCursorProperties(void)
287{
288 SDL_free(dbus_cursor_theme);
289 dbus_cursor_theme = NULL;
290}
291
292#endif
293
294static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time);
295struct wl_callback_listener cursor_frame_listener = {
296 cursor_frame_done
297};
298
299static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
300{
301 SDL_CursorData *c = (SDL_CursorData *)data;
302
303 const Uint64 now = SDL_GetTicks();
304 const Uint64 elapsed = (now - c->cursor_data.system.last_frame_callback_time_ms) % c->cursor_data.system.total_duration;
305 Uint64 advance = 0;
306 int next = c->cursor_data.system.current_frame;
307
308 wl_callback_destroy(cb);
309 c->cursor_data.system.frame_callback = wl_surface_frame(c->surface);
310 wl_callback_add_listener(c->cursor_data.system.frame_callback, &cursor_frame_listener, data);
311
312 c->cursor_data.system.current_frame_time_ms += elapsed;
313
314 // Calculate the next frame based on the elapsed duration.
315 for (Uint64 t = c->cursor_data.system.frames[next].duration; t <= c->cursor_data.system.current_frame_time_ms; t += c->cursor_data.system.frames[next].duration) {
316 next = (next + 1) % c->cursor_data.system.num_frames;
317 advance = t;
318
319 // Make sure we don't end up in an infinite loop if a cursor has frame durations of 0.
320 if (!c->cursor_data.system.frames[next].duration) {
321 break;
322 }
323 }
324
325 c->cursor_data.system.current_frame_time_ms -= advance;
326 c->cursor_data.system.last_frame_callback_time_ms = now;
327 c->cursor_data.system.current_frame = next;
328 wl_surface_attach(c->surface, c->cursor_data.system.frames[next].wl_buffer, 0, 0);
329 if (wl_surface_get_version(c->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
330 wl_surface_damage_buffer(c->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
331 } else {
332 wl_surface_damage(c->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
333 }
334 wl_surface_commit(c->surface);
335}
336
337static bool Wayland_GetSystemCursor(SDL_VideoData *vdata, SDL_CursorData *cdata, int *scale, int *dst_size, int *hot_x, int *hot_y)
338{
339 struct wl_cursor_theme *theme = NULL;
340 struct wl_cursor *cursor;
341 const char *css_name = "default";
342 const char *fallback_name = NULL;
343 double scale_factor = 1.0;
344 int theme_size = dbus_cursor_size;
345
346 // Fallback envvar if the DBus properties don't exist
347 if (theme_size <= 0) {
348 const char *xcursor_size = SDL_getenv("XCURSOR_SIZE");
349 if (xcursor_size) {
350 theme_size = SDL_atoi(xcursor_size);
351 }
352 }
353 if (theme_size <= 0) {
354 theme_size = 24;
355 }
356 // First, find the appropriate theme based on the current scale...
357 SDL_Window *focus = SDL_GetMouse()->focus;
358 if (focus) {
359 // TODO: Use the fractional scale once GNOME supports viewports on cursor surfaces.
360 scale_factor = SDL_ceil(focus->internal->scale_factor);
361 }
362
363 const int scaled_size = (int)SDL_lround(theme_size * scale_factor);
364 for (int i = 0; i < vdata->num_cursor_themes; ++i) {
365 if (vdata->cursor_themes[i].size == scaled_size) {
366 theme = vdata->cursor_themes[i].theme;
367 break;
368 }
369 }
370 if (!theme) {
371 const char *xcursor_theme = dbus_cursor_theme;
372
373 SDL_WaylandCursorTheme *new_cursor_themes = SDL_realloc(vdata->cursor_themes,
374 sizeof(SDL_WaylandCursorTheme) * (vdata->num_cursor_themes + 1));
375 if (!new_cursor_themes) {
376 return false;
377 }
378 vdata->cursor_themes = new_cursor_themes;
379
380 // Fallback envvar if the DBus properties don't exist
381 if (!xcursor_theme) {
382 xcursor_theme = SDL_getenv("XCURSOR_THEME");
383 }
384
385 theme = WAYLAND_wl_cursor_theme_load(xcursor_theme, scaled_size, vdata->shm);
386 vdata->cursor_themes[vdata->num_cursor_themes].size = scaled_size;
387 vdata->cursor_themes[vdata->num_cursor_themes++].theme = theme;
388 }
389
390 css_name = SDL_GetCSSCursorName(cdata->cursor_data.system.id, &fallback_name);
391 cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, css_name);
392 if (!cursor && fallback_name) {
393 cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, fallback_name);
394 }
395
396 // Fallback to the default cursor if the chosen one wasn't found
397 if (!cursor) {
398 cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "default");
399 }
400 // Try the old X11 name as a last resort
401 if (!cursor) {
402 cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "left_ptr");
403 }
404 if (!cursor) {
405 return false;
406 }
407
408 if (cdata->cursor_data.system.num_frames != cursor->image_count) {
409 SDL_free(cdata->cursor_data.system.frames);
410 cdata->cursor_data.system.frames = SDL_calloc(cursor->image_count, sizeof(Wayland_SystemCursorFrame));
411 if (!cdata->cursor_data.system.frames) {
412 return false;
413 }
414 }
415
416 // ... Set the cursor data, finally.
417 cdata->cursor_data.system.num_frames = cursor->image_count;
418 cdata->cursor_data.system.total_duration = 0;
419 for (int i = 0; i < cursor->image_count; ++i) {
420 cdata->cursor_data.system.frames[i].wl_buffer = WAYLAND_wl_cursor_image_get_buffer(cursor->images[i]);
421 cdata->cursor_data.system.frames[i].duration = cursor->images[i]->delay;
422 cdata->cursor_data.system.total_duration += cursor->images[i]->delay;
423 }
424
425 *scale = SDL_ceil(scale_factor) == scale_factor ? (int)scale_factor : 0;
426
427 if (scaled_size != cursor->images[0]->width) {
428 /* If the cursor size isn't an exact match for the target size, use a viewport
429 * to avoid a possible "Buffer size is not divisible by scale" protocol error.
430 *
431 * If viewports are unavailable, find an integer scale that works.
432 */
433 if (vdata->viewporter) {
434 // A scale of 0 indicates that a viewport set to the destination size should be used.
435 *scale = 0;
436 } else {
437 for (; *scale > 1; --*scale) {
438 if (cursor->images[0]->width % *scale == 0) {
439 break;
440 }
441 }
442 // Set the scale factor to the new value for the hotspot calculations.
443 scale_factor = *scale;
444 }
445 }
446
447 *dst_size = (int)SDL_lround(cursor->images[0]->width / scale_factor);
448
449 *hot_x = (int)SDL_lround(cursor->images[0]->hotspot_x / scale_factor);
450 *hot_y = (int)SDL_lround(cursor->images[0]->hotspot_y / scale_factor);
451
452 return true;
453}
454
455static Wayland_ScaledCustomCursor *Wayland_CacheScaledCustomCursor(SDL_CursorData *cdata, double scale)
456{
457 Wayland_ScaledCustomCursor *cache = NULL;
458
459 // Is this cursor already cached at the target scale?
460 if (!WAYLAND_wl_list_empty(&cdata->cursor_data.custom.scaled_cursor_cache)) {
461 Wayland_ScaledCustomCursor *c = NULL;
462 wl_list_for_each (c, &cdata->cursor_data.custom.scaled_cursor_cache, node) {
463 if (c->scale == scale) {
464 cache = c;
465 break;
466 }
467 }
468 }
469
470 if (!cache) {
471 cache = SDL_calloc(1, sizeof(Wayland_ScaledCustomCursor));
472 if (!cache) {
473 return NULL;
474 }
475
476 SDL_Surface *surface = SDL_GetSurfaceImage(cdata->cursor_data.custom.sdl_cursor_surface, (float)scale);
477 if (!surface) {
478 SDL_free(cache);
479 return NULL;
480 }
481
482 // Allocate the shared memory buffer for this cursor.
483 if (!Wayland_AllocSHMBuffer(surface->w, surface->h, &cache->shmBuffer)) {
484 SDL_free(cache);
485 SDL_DestroySurface(surface);
486 return NULL;
487 }
488
489 // Wayland requires premultiplied alpha for its surfaces.
490 SDL_PremultiplyAlpha(surface->w, surface->h,
491 surface->format, surface->pixels, surface->pitch,
492 SDL_PIXELFORMAT_ARGB8888, cache->shmBuffer.shm_data, surface->w * 4, true);
493
494 cache->scale = scale;
495 WAYLAND_wl_list_insert(&cdata->cursor_data.custom.scaled_cursor_cache, &cache->node);
496 SDL_DestroySurface(surface);
497 }
498
499 return cache;
500}
501
502static bool Wayland_GetCustomCursor(SDL_Cursor *cursor, struct wl_buffer **buffer, int *scale, int *dst_width, int *dst_height, int *hot_x, int *hot_y)
503{
504 SDL_VideoDevice *vd = SDL_GetVideoDevice();
505 SDL_VideoData *wd = vd->internal;
506 SDL_CursorData *data = cursor->internal;
507 SDL_Window *focus = SDL_GetMouseFocus();
508 double scale_factor = 1.0;
509
510 if (focus && SDL_SurfaceHasAlternateImages(data->cursor_data.custom.sdl_cursor_surface)) {
511 scale_factor = focus->internal->scale_factor;
512 }
513
514 // Only use fractional scale values if viewports are available.
515 if (!wd->viewporter) {
516 scale_factor = SDL_ceil(scale_factor);
517 }
518
519 Wayland_ScaledCustomCursor *c = Wayland_CacheScaledCustomCursor(data, scale_factor);
520 if (!c) {
521 return false;
522 }
523
524 *buffer = c->shmBuffer.wl_buffer;
525 *scale = SDL_ceil(scale_factor) == scale_factor ? (int)scale_factor : 0;
526 *dst_width = data->cursor_data.custom.sdl_cursor_surface->w;
527 *dst_height = data->cursor_data.custom.sdl_cursor_surface->h;
528 *hot_x = data->cursor_data.custom.hot_x;
529 *hot_y = data->cursor_data.custom.hot_y;
530
531 return true;
532}
533
534static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
535{
536 SDL_VideoDevice *vd = SDL_GetVideoDevice();
537 SDL_VideoData *wd = vd->internal;
538
539 SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor));
540 if (cursor) {
541 SDL_CursorData *data = SDL_calloc(1, sizeof(*data));
542 if (!data) {
543 SDL_free(cursor);
544 return NULL;
545 }
546 cursor->internal = data;
547 WAYLAND_wl_list_init(&data->cursor_data.custom.scaled_cursor_cache);
548 data->cursor_data.custom.hot_x = hot_x;
549 data->cursor_data.custom.hot_y = hot_y;
550 data->surface = wl_compositor_create_surface(wd->compositor);
551
552 data->cursor_data.custom.sdl_cursor_surface = surface;
553 ++surface->refcount;
554
555 // If the cursor has only one size, just prepare it now.
556 if (!SDL_SurfaceHasAlternateImages(surface)) {
557 Wayland_CacheScaledCustomCursor(data, 1.0);
558 }
559 }
560
561 return cursor;
562}
563
564static SDL_Cursor *Wayland_CreateSystemCursor(SDL_SystemCursor id)
565{
566 SDL_VideoData *data = SDL_GetVideoDevice()->internal;
567 SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor));
568 if (cursor) {
569 SDL_CursorData *cdata = SDL_calloc(1, sizeof(*cdata));
570 if (!cdata) {
571 SDL_free(cursor);
572 return NULL;
573 }
574 cursor->internal = cdata;
575
576 /* The surface is only necessary if the cursor shape manager is not present.
577 *
578 * Note that we can't actually set any other cursor properties, as this
579 * is window-specific. See Wayland_GetSystemCursor for the rest!
580 */
581 if (!data->cursor_shape_manager) {
582 cdata->surface = wl_compositor_create_surface(data->compositor);
583 wl_surface_set_user_data(cdata->surface, NULL);
584 }
585
586 cdata->cursor_data.system.id = id;
587 cdata->is_system_cursor = true;
588 }
589
590 return cursor;
591}
592
593static SDL_Cursor *Wayland_CreateDefaultCursor(void)
594{
595 SDL_SystemCursor id = SDL_GetDefaultSystemCursor();
596 return Wayland_CreateSystemCursor(id);
597}
598
599static void Wayland_FreeCursorData(SDL_CursorData *d)
600{
601 SDL_VideoDevice *vd = SDL_GetVideoDevice();
602 struct SDL_WaylandInput *input = vd->internal->input;
603
604 if (input->current_cursor == d) {
605 input->current_cursor = NULL;
606 }
607
608 // Buffers for system cursors must not be destroyed.
609 if (d->is_system_cursor) {
610 if (d->cursor_data.system.frame_callback) {
611 wl_callback_destroy(d->cursor_data.system.frame_callback);
612 }
613 SDL_free(d->cursor_data.system.frames);
614 } else {
615 Wayland_ScaledCustomCursor *c, *temp;
616 wl_list_for_each_safe(c, temp, &d->cursor_data.custom.scaled_cursor_cache, node) {
617 Wayland_ReleaseSHMBuffer(&c->shmBuffer);
618 SDL_free(c);
619 }
620
621 SDL_DestroySurface(d->cursor_data.custom.sdl_cursor_surface);
622 }
623
624 if (d->viewport) {
625 wp_viewport_destroy(d->viewport);
626 d->viewport = NULL;
627 }
628
629 if (d->surface) {
630 wl_surface_destroy(d->surface);
631 d->surface = NULL;
632 }
633}
634
635static void Wayland_FreeCursor(SDL_Cursor *cursor)
636{
637 if (!cursor) {
638 return;
639 }
640
641 // Probably not a cursor we own
642 if (!cursor->internal) {
643 return;
644 }
645
646 Wayland_FreeCursorData(cursor->internal);
647
648 SDL_free(cursor->internal);
649 SDL_free(cursor);
650}
651
652static void Wayland_SetSystemCursorShape(struct SDL_WaylandInput *input, SDL_SystemCursor id)
653{
654 Uint32 shape;
655
656 switch (id) {
657 case SDL_SYSTEM_CURSOR_DEFAULT:
658 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
659 break;
660 case SDL_SYSTEM_CURSOR_TEXT:
661 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT;
662 break;
663 case SDL_SYSTEM_CURSOR_WAIT:
664 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT;
665 break;
666 case SDL_SYSTEM_CURSOR_CROSSHAIR:
667 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR;
668 break;
669 case SDL_SYSTEM_CURSOR_PROGRESS:
670 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS;
671 break;
672 case SDL_SYSTEM_CURSOR_NWSE_RESIZE:
673 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE;
674 break;
675 case SDL_SYSTEM_CURSOR_NESW_RESIZE:
676 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE;
677 break;
678 case SDL_SYSTEM_CURSOR_EW_RESIZE:
679 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE;
680 break;
681 case SDL_SYSTEM_CURSOR_NS_RESIZE:
682 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE;
683 break;
684 case SDL_SYSTEM_CURSOR_MOVE:
685 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL;
686 break;
687 case SDL_SYSTEM_CURSOR_NOT_ALLOWED:
688 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED;
689 break;
690 case SDL_SYSTEM_CURSOR_POINTER:
691 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER;
692 break;
693 case SDL_SYSTEM_CURSOR_NW_RESIZE:
694 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE;
695 break;
696 case SDL_SYSTEM_CURSOR_N_RESIZE:
697 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE;
698 break;
699 case SDL_SYSTEM_CURSOR_NE_RESIZE:
700 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE;
701 break;
702 case SDL_SYSTEM_CURSOR_E_RESIZE:
703 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE;
704 break;
705 case SDL_SYSTEM_CURSOR_SE_RESIZE:
706 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE;
707 break;
708 case SDL_SYSTEM_CURSOR_S_RESIZE:
709 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE;
710 break;
711 case SDL_SYSTEM_CURSOR_SW_RESIZE:
712 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE;
713 break;
714 case SDL_SYSTEM_CURSOR_W_RESIZE:
715 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE;
716 break;
717 default:
718 SDL_assert(0); // Should never be here...
719 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
720 }
721
722 wp_cursor_shape_device_v1_set_shape(input->cursor_shape, input->pointer_enter_serial, shape);
723}
724
725static bool Wayland_ShowCursor(SDL_Cursor *cursor)
726{
727 SDL_VideoDevice *vd = SDL_GetVideoDevice();
728 SDL_VideoData *d = vd->internal;
729 struct SDL_WaylandInput *input = d->input;
730 struct wl_pointer *pointer = d->pointer;
731 struct wl_buffer *buffer = NULL;
732 int scale = 1;
733 int dst_width = 0;
734 int dst_height = 0;
735 int hot_x;
736 int hot_y;
737
738 if (!pointer) {
739 return false;
740 }
741
742 // Stop the frame callback for old animated cursors.
743 if (input->current_cursor && input->current_cursor->is_system_cursor &&
744 input->current_cursor->cursor_data.system.frame_callback) {
745 wl_callback_destroy(input->current_cursor->cursor_data.system.frame_callback);
746 input->current_cursor->cursor_data.system.frame_callback = NULL;
747 }
748
749 if (cursor) {
750 SDL_CursorData *data = cursor->internal;
751
752 if (data->is_system_cursor) {
753 // If the cursor shape protocol is supported, the compositor will draw nicely scaled cursors for us, so nothing more to do.
754 if (input->cursor_shape) {
755 Wayland_SetSystemCursorShape(input, data->cursor_data.system.id);
756 input->current_cursor = data;
757 return true;
758 }
759
760 if (!Wayland_GetSystemCursor(d, data, &scale, &dst_width, &hot_x, &hot_y)) {
761 return false;
762 }
763
764 dst_height = dst_width;
765 wl_surface_attach(data->surface, data->cursor_data.system.frames[0].wl_buffer, 0, 0);
766
767 // If more than one frame is available, create a frame callback to run the animation.
768 if (data->cursor_data.system.num_frames > 1) {
769 data->cursor_data.system.last_frame_callback_time_ms = SDL_GetTicks();
770 data->cursor_data.system.current_frame_time_ms = 0;
771 data->cursor_data.system.current_frame = 0;
772 data->cursor_data.system.frame_callback = wl_surface_frame(data->surface);
773 wl_callback_add_listener(data->cursor_data.system.frame_callback, &cursor_frame_listener, data);
774 }
775 } else {
776 if (!Wayland_GetCustomCursor(cursor, &buffer, &scale, &dst_width, &dst_height, &hot_x, &hot_y)) {
777 return false;
778 }
779
780 wl_surface_attach(data->surface, buffer, 0, 0);
781 }
782
783 // A scale value of 0 indicates that a viewport with the returned destination size should be used.
784 if (!scale) {
785 if (!data->viewport) {
786 data->viewport = wp_viewporter_get_viewport(d->viewporter, data->surface);
787 }
788 wl_surface_set_buffer_scale(data->surface, 1);
789 wp_viewport_set_source(data->viewport, wl_fixed_from_int(-1), wl_fixed_from_int(-1), wl_fixed_from_int(-1), wl_fixed_from_int(-1));
790 wp_viewport_set_destination(data->viewport, dst_width, dst_height);
791 } else {
792 if (data->viewport) {
793 wp_viewport_destroy(data->viewport);
794 data->viewport = NULL;
795 }
796 wl_surface_set_buffer_scale(data->surface, scale);
797 }
798
799 wl_pointer_set_cursor(pointer, input->pointer_enter_serial, data->surface, hot_x, hot_y);
800
801 if (wl_surface_get_version(data->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
802 wl_surface_damage_buffer(data->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
803 } else {
804 wl_surface_damage(data->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
805 }
806
807 wl_surface_commit(data->surface);
808 input->current_cursor = data;
809 } else {
810 input->current_cursor = NULL;
811 wl_pointer_set_cursor(pointer, input->pointer_enter_serial, NULL, 0, 0);
812 }
813
814 return true;
815}
816
817static bool Wayland_WarpMouse(SDL_Window *window, float x, float y)
818{
819 SDL_VideoDevice *vd = SDL_GetVideoDevice();
820 SDL_VideoData *d = vd->internal;
821 SDL_WindowData *wind = window->internal;
822 struct SDL_WaylandInput *input = d->input;
823
824 if (d->pointer_constraints) {
825 const bool toggle_lock = !wind->locked_pointer;
826
827 /* The pointer confinement protocol allows setting a hint to warp the pointer,
828 * but only when the pointer is locked.
829 *
830 * Lock the pointer, set the position hint, unlock, and hope for the best.
831 */
832 if (toggle_lock) {
833 Wayland_input_lock_pointer(input, window);
834 }
835 if (wind->locked_pointer) {
836 const wl_fixed_t f_x = wl_fixed_from_double(x / wind->pointer_scale.x);
837 const wl_fixed_t f_y = wl_fixed_from_double(y / wind->pointer_scale.y);
838 zwp_locked_pointer_v1_set_cursor_position_hint(wind->locked_pointer, f_x, f_y);
839 wl_surface_commit(wind->surface);
840 }
841 if (toggle_lock) {
842 Wayland_input_unlock_pointer(input, window);
843 }
844
845 /* NOTE: There is a pending warp event under discussion that should replace this when available.
846 * https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/340
847 */
848 SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, x, y);
849 } else {
850 return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required zwp_pointer_confinement_v1 protocol");
851 }
852
853 return true;
854}
855
856static bool Wayland_WarpMouseGlobal(float x, float y)
857{
858 SDL_VideoDevice *vd = SDL_GetVideoDevice();
859 SDL_VideoData *d = vd->internal;
860 struct SDL_WaylandInput *input = d->input;
861 SDL_WindowData *wind = input->pointer_focus;
862
863 // If the client wants the coordinates warped to within the focused window, just convert the coordinates to relative.
864 if (wind) {
865 SDL_Window *window = wind->sdlwindow;
866 return Wayland_WarpMouse(window, x - (float)window->x, y - (float)window->y);
867 }
868
869 return SDL_SetError("wayland: can't warp the mouse when a window does not have focus");
870}
871
872static bool Wayland_SetRelativeMouseMode(bool enabled)
873{
874 SDL_VideoDevice *vd = SDL_GetVideoDevice();
875 SDL_VideoData *data = vd->internal;
876
877 if (enabled) {
878 return Wayland_input_enable_relative_pointer(data->input);
879 } else {
880 return Wayland_input_disable_relative_pointer(data->input);
881 }
882}
883
884/* Wayland doesn't support getting the true global cursor position, but it can
885 * be faked well enough for what most applications use it for: querying the
886 * global cursor coordinates and transforming them to the window-relative
887 * coordinates manually.
888 *
889 * The global position is derived by taking the cursor position relative to the
890 * toplevel window, and offsetting it by the origin of the output the window is
891 * currently considered to be on. The cursor position and button state when the
892 * cursor is outside an application window are unknown, but this gives 'correct'
893 * coordinates when the window has focus, which is good enough for most
894 * applications.
895 */
896static SDL_MouseButtonFlags SDLCALL Wayland_GetGlobalMouseState(float *x, float *y)
897{
898 SDL_Window *focus = SDL_GetMouseFocus();
899 SDL_MouseButtonFlags result = 0;
900
901 if (focus) {
902 SDL_VideoData *viddata = SDL_GetVideoDevice()->internal;
903 int off_x, off_y;
904
905 result = viddata->input->buttons_pressed;
906 SDL_RelativeToGlobalForWindow(focus, focus->x, focus->y, &off_x, &off_y);
907 *x += off_x;
908 *y += off_y;
909 }
910
911 return result;
912}
913
914#if 0 // TODO RECONNECT: See waylandvideo.c for more information!
915static void Wayland_RecreateCursor(SDL_Cursor *cursor, SDL_VideoData *vdata)
916{
917 SDL_CursorData *cdata = cursor->internal;
918
919 // Probably not a cursor we own
920 if (cdata == NULL) {
921 return;
922 }
923
924 Wayland_FreeCursorData(cdata);
925
926 // We're not currently freeing this, so... yolo?
927 if (cdata->shm_data != NULL) {
928 void *old_data_pointer = cdata->shm_data;
929 int stride = cdata->w * 4;
930
931 create_buffer_from_shm(cdata, cdata->w, cdata->h, WL_SHM_FORMAT_ARGB8888);
932
933 SDL_memcpy(cdata->shm_data, old_data_pointer, stride * cdata->h);
934 }
935 cdata->surface = wl_compositor_create_surface(vdata->compositor);
936 wl_surface_set_user_data(cdata->surface, NULL);
937}
938
939void Wayland_RecreateCursors(void)
940{
941 SDL_Cursor *cursor;
942 SDL_Mouse *mouse = SDL_GetMouse();
943 SDL_VideoData *vdata = SDL_GetVideoDevice()->internal;
944
945 if (vdata && vdata->cursor_themes) {
946 SDL_free(vdata->cursor_themes);
947 vdata->cursor_themes = NULL;
948 vdata->num_cursor_themes = 0;
949 }
950
951 if (mouse == NULL) {
952 return;
953 }
954
955 for (cursor = mouse->cursors; cursor != NULL; cursor = cursor->next) {
956 Wayland_RecreateCursor(cursor, vdata);
957 }
958 if (mouse->def_cursor) {
959 Wayland_RecreateCursor(mouse->def_cursor, vdata);
960 }
961 if (mouse->cur_cursor) {
962 Wayland_RecreateCursor(mouse->cur_cursor, vdata);
963 if (mouse->cursor_shown) {
964 Wayland_ShowCursor(mouse->cur_cursor);
965 }
966 }
967}
968#endif // 0
969
970void Wayland_InitMouse(void)
971{
972 SDL_Mouse *mouse = SDL_GetMouse();
973 SDL_VideoDevice *vd = SDL_GetVideoDevice();
974 SDL_VideoData *d = vd->internal;
975
976 mouse->CreateCursor = Wayland_CreateCursor;
977 mouse->CreateSystemCursor = Wayland_CreateSystemCursor;
978 mouse->ShowCursor = Wayland_ShowCursor;
979 mouse->FreeCursor = Wayland_FreeCursor;
980 mouse->WarpMouse = Wayland_WarpMouse;
981 mouse->WarpMouseGlobal = Wayland_WarpMouseGlobal;
982 mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode;
983 mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState;
984
985 SDL_HitTestResult r = SDL_HITTEST_NORMAL;
986 while (r <= SDL_HITTEST_RESIZE_LEFT) {
987 switch (r) {
988 case SDL_HITTEST_NORMAL:
989 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT);
990 break;
991 case SDL_HITTEST_DRAGGABLE:
992 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT);
993 break;
994 case SDL_HITTEST_RESIZE_TOPLEFT:
995 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_NW_RESIZE);
996 break;
997 case SDL_HITTEST_RESIZE_TOP:
998 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_N_RESIZE);
999 break;
1000 case SDL_HITTEST_RESIZE_TOPRIGHT:
1001 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_NE_RESIZE);
1002 break;
1003 case SDL_HITTEST_RESIZE_RIGHT:
1004 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_E_RESIZE);
1005 break;
1006 case SDL_HITTEST_RESIZE_BOTTOMRIGHT:
1007 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SE_RESIZE);
1008 break;
1009 case SDL_HITTEST_RESIZE_BOTTOM:
1010 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_S_RESIZE);
1011 break;
1012 case SDL_HITTEST_RESIZE_BOTTOMLEFT:
1013 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SW_RESIZE);
1014 break;
1015 case SDL_HITTEST_RESIZE_LEFT:
1016 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_W_RESIZE);
1017 break;
1018 }
1019 r++;
1020 }
1021
1022#ifdef SDL_USE_LIBDBUS
1023 /* The DBus cursor properties are only needed when manually loading themes and cursors.
1024 * If the cursor shape protocol is present, the compositor will handle it internally.
1025 */
1026 if (!d->cursor_shape_manager) {
1027 Wayland_DBusInitCursorProperties(d);
1028 }
1029#endif
1030
1031 SDL_SetDefaultCursor(Wayland_CreateDefaultCursor());
1032}
1033
1034void Wayland_FiniMouse(SDL_VideoData *data)
1035{
1036 Wayland_FreeCursorThemes(data);
1037
1038#ifdef SDL_USE_LIBDBUS
1039 Wayland_DBusFinishCursorProperties();
1040#endif
1041
1042 for (int i = 0; i < SDL_arraysize(sys_cursors); i++) {
1043 Wayland_FreeCursor(sys_cursors[i]);
1044 sys_cursors[i] = NULL;
1045 }
1046}
1047
1048void Wayland_SetHitTestCursor(SDL_HitTestResult rc)
1049{
1050 if (rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) {
1051 SDL_SetCursor(NULL);
1052 } else {
1053 Wayland_ShowCursor(sys_cursors[rc]);
1054 }
1055}
1056
1057#endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmouse.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmouse.h
new file mode 100644
index 0000000..58188c0
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandmouse.h
@@ -0,0 +1,34 @@
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#ifndef SDL_waylandmouse_h_
25#define SDL_waylandmouse_h_
26
27extern void Wayland_InitMouse(void);
28extern void Wayland_FiniMouse(SDL_VideoData *data);
29extern void Wayland_SetHitTestCursor(SDL_HitTestResult rc);
30#if 0 // TODO RECONNECT: See waylandvideo.c for more information!
31extern void Wayland_RecreateCursors(void);
32#endif // 0
33
34#endif
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandopengles.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandopengles.c
new file mode 100644
index 0000000..ac2b06d
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandopengles.c
@@ -0,0 +1,220 @@
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#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WAYLAND) && defined(SDL_VIDEO_OPENGL_EGL)
24
25#include "../../core/unix/SDL_poll.h"
26#include "../SDL_sysvideo.h"
27#include "../../events/SDL_windowevents_c.h"
28#include "SDL_waylandvideo.h"
29#include "SDL_waylandopengles.h"
30#include "SDL_waylandwindow.h"
31#include "SDL_waylandevents_c.h"
32
33#include "xdg-shell-client-protocol.h"
34
35// EGL implementation of SDL OpenGL ES support
36
37bool Wayland_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path)
38{
39 bool result;
40 SDL_VideoData *data = _this->internal;
41
42 result = SDL_EGL_LoadLibrary(_this, path, (NativeDisplayType)data->display, _this->gl_config.egl_platform);
43
44 Wayland_PumpEvents(_this);
45 WAYLAND_wl_display_flush(data->display);
46
47 return result;
48}
49
50SDL_GLContext Wayland_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window)
51{
52 SDL_GLContext context;
53 context = SDL_EGL_CreateContext(_this, window->internal->egl_surface);
54 WAYLAND_wl_display_flush(_this->internal->display);
55
56 return context;
57}
58
59/* Wayland wants to tell you when to provide new frames, and if you have a non-zero
60 swap interval, Mesa will block until a callback tells it to do so. On some
61 compositors, they might decide that a minimized window _never_ gets a callback,
62 which causes apps to hang during swapping forever. So we always set the official
63 eglSwapInterval to zero to avoid blocking inside EGL, and manage this ourselves.
64 If a swap blocks for too long waiting on a callback, we just go on, under the
65 assumption the frame will be wasted, but this is better than freezing the app.
66 I frown upon platforms that dictate this sort of control inversion (the callback
67 is intended for _rendering_, not stalling until vsync), but we can work around
68 this for now. --ryan. */
69/* Addendum: several recent APIs demand this sort of control inversion: Emscripten,
70 libretro, Wayland, probably others...it feels like we're eventually going to have
71 to give in with a future SDL API revision, since we can bend the other APIs to
72 this style, but this style is much harder to bend the other way. :/ */
73bool Wayland_GLES_SetSwapInterval(SDL_VideoDevice *_this, int interval)
74{
75 if (!_this->egl_data) {
76 return SDL_SetError("EGL not initialized");
77 }
78
79 /* technically, this is _all_ adaptive vsync (-1), because we can't
80 actually wait for the _next_ vsync if you set 1, but things that
81 request 1 probably won't care _that_ much. I hope. No matter what
82 you do, though, you never see tearing on Wayland. */
83 if (interval > 1) {
84 interval = 1;
85 } else if (interval < -1) {
86 interval = -1;
87 }
88
89 // !!! FIXME: technically, this should be per-context, right?
90 _this->egl_data->egl_swapinterval = interval;
91 _this->egl_data->eglSwapInterval(_this->egl_data->egl_display, 0);
92 return true;
93}
94
95bool Wayland_GLES_GetSwapInterval(SDL_VideoDevice *_this, int *interval)
96{
97 if (!_this->egl_data) {
98 return SDL_SetError("EGL not initialized");
99 }
100
101 *interval =_this->egl_data->egl_swapinterval;
102 return true;
103}
104
105bool Wayland_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
106{
107 SDL_WindowData *data = window->internal;
108 const int swap_interval = _this->egl_data->egl_swapinterval;
109
110 /* For windows that we know are hidden, skip swaps entirely, if we don't do
111 * this compositors will intentionally stall us indefinitely and there's no
112 * way for an end user to show the window, unlike other situations (i.e.
113 * the window is minimized, behind another window, etc.).
114 *
115 * FIXME: Request EGL_WAYLAND_swap_buffers_with_timeout.
116 * -flibit
117 */
118 if (data->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN &&
119 data->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME) {
120 return true;
121 }
122
123 /* By default, we wait for the Wayland frame callback and then issue the pageflip (eglSwapBuffers),
124 * but if we want low latency (double buffer scheme), we issue the pageflip and then wait
125 * immediately for the Wayland frame callback.
126 */
127 if (data->double_buffer) {
128 // Feed the frame to Wayland. This will set it so the wl_surface_frame callback can fire again.
129 if (!_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, data->egl_surface)) {
130 return SDL_EGL_SetError("unable to show color buffer in an OS-native window", "eglSwapBuffers");
131 }
132
133 WAYLAND_wl_display_flush(data->waylandData->display);
134 }
135
136 // Control swap interval ourselves. See comments on Wayland_GLES_SetSwapInterval
137 if (swap_interval != 0 && data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOWN) {
138 SDL_VideoData *videodata = _this->internal;
139 struct wl_display *display = videodata->display;
140 // 20hz, so we'll progress even if throttled to zero.
141 const Uint64 max_wait = SDL_GetTicksNS() + (SDL_NS_PER_SECOND / 20);
142 while (SDL_GetAtomicInt(&data->swap_interval_ready) == 0) {
143 Uint64 now;
144
145 WAYLAND_wl_display_flush(display);
146
147 /* wl_display_prepare_read_queue() will return false if the event queue is not empty.
148 * If the event queue is empty, it will prepare us for our SDL_IOReady() call. */
149 if (WAYLAND_wl_display_prepare_read_queue(display, data->gles_swap_frame_event_queue) != 0) {
150 // We have some pending events. Check if the frame callback happened.
151 WAYLAND_wl_display_dispatch_queue_pending(display, data->gles_swap_frame_event_queue);
152 continue;
153 }
154
155 // Beyond this point, we must either call wl_display_cancel_read() or wl_display_read_events()
156
157 now = SDL_GetTicksNS();
158 if (now >= max_wait) {
159 // Timeout expired. Cancel the read.
160 WAYLAND_wl_display_cancel_read(display);
161 break;
162 }
163
164 if (SDL_IOReady(WAYLAND_wl_display_get_fd(display), SDL_IOR_READ, max_wait - now) <= 0) {
165 // Error or timeout expired without any events for us. Cancel the read.
166 WAYLAND_wl_display_cancel_read(display);
167 break;
168 }
169
170 // We have events. Read and dispatch them.
171 WAYLAND_wl_display_read_events(display);
172 WAYLAND_wl_display_dispatch_queue_pending(display, data->gles_swap_frame_event_queue);
173 }
174 SDL_SetAtomicInt(&data->swap_interval_ready, 0);
175 }
176
177 if (!data->double_buffer) {
178 // Feed the frame to Wayland. This will set it so the wl_surface_frame callback can fire again.
179 if (!_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, data->egl_surface)) {
180 return SDL_EGL_SetError("unable to show color buffer in an OS-native window", "eglSwapBuffers");
181 }
182
183 WAYLAND_wl_display_flush(data->waylandData->display);
184 }
185
186 return true;
187}
188
189bool Wayland_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context)
190{
191 bool result;
192
193 if (window && context) {
194 result = SDL_EGL_MakeCurrent(_this, window->internal->egl_surface, context);
195 } else {
196 result = SDL_EGL_MakeCurrent(_this, NULL, NULL);
197 }
198
199 WAYLAND_wl_display_flush(_this->internal->display);
200
201 _this->egl_data->eglSwapInterval(_this->egl_data->egl_display, 0); // see comments on Wayland_GLES_SetSwapInterval.
202
203 return result;
204}
205
206bool Wayland_GLES_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context)
207{
208 bool result = SDL_EGL_DestroyContext(_this, context);
209 WAYLAND_wl_display_flush(_this->internal->display);
210 return result;
211}
212
213EGLSurface Wayland_GLES_GetEGLSurface(SDL_VideoDevice *_this, SDL_Window *window)
214{
215 SDL_WindowData *windowdata = window->internal;
216
217 return windowdata->egl_surface;
218}
219
220#endif // SDL_VIDEO_DRIVER_WAYLAND && SDL_VIDEO_OPENGL_EGL
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandopengles.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandopengles.h
new file mode 100644
index 0000000..ed2e927
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandopengles.h
@@ -0,0 +1,48 @@
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#include "SDL_internal.h"
22
23#ifndef SDL_waylandopengles_h_
24#define SDL_waylandopengles_h_
25
26#include "../SDL_sysvideo.h"
27#include "../SDL_egl_c.h"
28
29typedef struct SDL_PrivateGLESData
30{
31 int dummy;
32} SDL_PrivateGLESData;
33
34// OpenGLES functions
35#define Wayland_GLES_GetAttribute SDL_EGL_GetAttribute
36#define Wayland_GLES_GetProcAddress SDL_EGL_GetProcAddressInternal
37#define Wayland_GLES_UnloadLibrary SDL_EGL_UnloadLibrary
38
39extern bool Wayland_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path);
40extern SDL_GLContext Wayland_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window);
41extern bool Wayland_GLES_SetSwapInterval(SDL_VideoDevice *_this, int interval);
42extern bool Wayland_GLES_GetSwapInterval(SDL_VideoDevice *_this, int *interval);
43extern bool Wayland_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window);
44extern bool Wayland_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context);
45extern bool Wayland_GLES_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context);
46extern SDL_EGLSurface Wayland_GLES_GetEGLSurface(SDL_VideoDevice *_this, SDL_Window *window);
47
48#endif // SDL_waylandopengles_h_
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandshmbuffer.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandshmbuffer.c
new file mode 100644
index 0000000..8fc6d9e
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandshmbuffer.c
@@ -0,0 +1,170 @@
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 <errno.h>
27#include <fcntl.h>
28#include <limits.h>
29#include <signal.h>
30#include <sys/mman.h>
31#include <unistd.h>
32
33#include "SDL_waylandshmbuffer.h"
34#include "SDL_waylandvideo.h"
35
36static bool SetTempFileSize(int fd, off_t size)
37{
38#ifdef HAVE_POSIX_FALLOCATE
39 sigset_t set, old_set;
40 int ret;
41
42 /* SIGALRM can potentially block a large posix_fallocate() operation
43 * from succeeding, so block it.
44 */
45 sigemptyset(&set);
46 sigaddset(&set, SIGALRM);
47 sigprocmask(SIG_BLOCK, &set, &old_set);
48
49 do {
50 ret = posix_fallocate(fd, 0, size);
51 } while (ret == EINTR);
52
53 sigprocmask(SIG_SETMASK, &old_set, NULL);
54
55 if (ret == 0) {
56 return true;
57 } else if (ret != EINVAL && errno != EOPNOTSUPP) {
58 return false;
59 }
60#endif
61
62 if (ftruncate(fd, size) < 0) {
63 return false;
64 }
65 return true;
66}
67
68static int CreateTempFD(off_t size)
69{
70 int fd;
71
72#ifdef HAVE_MEMFD_CREATE
73 fd = memfd_create("SDL", MFD_CLOEXEC | MFD_ALLOW_SEALING);
74 if (fd >= 0) {
75 fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
76 } else
77#endif
78 {
79 static const char template[] = "/sdl-shared-XXXXXX";
80 const char *xdg_path;
81 char tmp_path[PATH_MAX];
82
83 xdg_path = SDL_getenv("XDG_RUNTIME_DIR");
84 if (!xdg_path) {
85 return -1;
86 }
87
88 SDL_strlcpy(tmp_path, xdg_path, PATH_MAX);
89 SDL_strlcat(tmp_path, template, PATH_MAX);
90
91 fd = mkostemp(tmp_path, O_CLOEXEC);
92 if (fd < 0) {
93 return -1;
94 }
95
96 // Need to manually unlink the temp files, or they can persist after close and fill up the temp storage.
97 unlink(tmp_path);
98 }
99
100 if (!SetTempFileSize(fd, size)) {
101 close(fd);
102 return -1;
103 }
104
105 return fd;
106}
107
108static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer)
109{
110 // NOP
111}
112
113static struct wl_buffer_listener buffer_listener = {
114 buffer_handle_release
115};
116
117bool Wayland_AllocSHMBuffer(int width, int height, struct Wayland_SHMBuffer *shmBuffer)
118{
119 SDL_VideoDevice *vd = SDL_GetVideoDevice();
120 SDL_VideoData *data = vd->internal;
121 struct wl_shm_pool *shm_pool;
122 const Uint32 SHM_FMT = WL_SHM_FORMAT_ARGB8888;
123
124 if (!shmBuffer) {
125 return SDL_InvalidParamError("shmBuffer");
126 }
127
128 const int stride = width * 4;
129 shmBuffer->shm_data_size = stride * height;
130
131 const int shm_fd = CreateTempFD(shmBuffer->shm_data_size);
132 if (shm_fd < 0) {
133 return SDL_SetError("Creating SHM buffer failed.");
134 }
135
136 shmBuffer->shm_data = mmap(NULL, shmBuffer->shm_data_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
137 if (shmBuffer->shm_data == MAP_FAILED) {
138 shmBuffer->shm_data = NULL;
139 close(shm_fd);
140 return SDL_SetError("mmap() failed.");
141 }
142
143 SDL_assert(shmBuffer->shm_data != NULL);
144
145 shm_pool = wl_shm_create_pool(data->shm, shm_fd, shmBuffer->shm_data_size);
146 shmBuffer->wl_buffer = wl_shm_pool_create_buffer(shm_pool, 0, width, height, stride, SHM_FMT);
147 wl_buffer_add_listener(shmBuffer->wl_buffer, &buffer_listener, shmBuffer);
148
149 wl_shm_pool_destroy(shm_pool);
150 close(shm_fd);
151
152 return true;
153}
154
155void Wayland_ReleaseSHMBuffer(struct Wayland_SHMBuffer *shmBuffer)
156{
157 if (shmBuffer) {
158 if (shmBuffer->wl_buffer) {
159 wl_buffer_destroy(shmBuffer->wl_buffer);
160 shmBuffer->wl_buffer = NULL;
161 }
162 if (shmBuffer->shm_data) {
163 munmap(shmBuffer->shm_data, shmBuffer->shm_data_size);
164 shmBuffer->shm_data = NULL;
165 }
166 shmBuffer->shm_data_size = 0;
167 }
168}
169
170#endif
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandshmbuffer.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandshmbuffer.h
new file mode 100644
index 0000000..f6f49a4
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandshmbuffer.h
@@ -0,0 +1,38 @@
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#ifndef SDL_waylandshmbuffer_h_
25#define SDL_waylandshmbuffer_h_
26
27struct Wayland_SHMBuffer
28{
29 struct wl_buffer *wl_buffer;
30 void *shm_data;
31 int shm_data_size;
32};
33
34// Allocates an SHM buffer with the format WL_SHM_FORMAT_ARGB8888
35extern bool Wayland_AllocSHMBuffer(int width, int height, struct Wayland_SHMBuffer *shmBuffer);
36extern void Wayland_ReleaseSHMBuffer(struct Wayland_SHMBuffer *shmBuffer);
37
38#endif
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandsym.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandsym.h
new file mode 100644
index 0000000..846c876
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandsym.h
@@ -0,0 +1,242 @@
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/* *INDENT-OFF* */ // clang-format off
23
24#ifndef SDL_WAYLAND_MODULE
25#define SDL_WAYLAND_MODULE(modname)
26#endif
27
28#ifndef SDL_WAYLAND_SYM
29#define SDL_WAYLAND_SYM(rc,fn,params)
30#endif
31
32#ifndef SDL_WAYLAND_SYM_OPT
33#define SDL_WAYLAND_SYM_OPT(rc,fn,params)
34#endif
35
36#ifndef SDL_WAYLAND_INTERFACE
37#define SDL_WAYLAND_INTERFACE(iface)
38#endif
39
40SDL_WAYLAND_MODULE(WAYLAND_CLIENT)
41SDL_WAYLAND_SYM(void, wl_proxy_marshal, (struct wl_proxy *, uint32_t, ...))
42SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_create, (struct wl_proxy *, const struct wl_interface *))
43SDL_WAYLAND_SYM(void, wl_proxy_destroy, (struct wl_proxy *))
44SDL_WAYLAND_SYM(int, wl_proxy_add_listener, (struct wl_proxy *, void (**)(void), void *))
45SDL_WAYLAND_SYM(void, wl_proxy_set_user_data, (struct wl_proxy *, void *))
46SDL_WAYLAND_SYM(void *, wl_proxy_get_user_data, (struct wl_proxy *))
47SDL_WAYLAND_SYM(uint32_t, wl_proxy_get_version, (struct wl_proxy *))
48SDL_WAYLAND_SYM(uint32_t, wl_proxy_get_id, (struct wl_proxy *))
49SDL_WAYLAND_SYM(const char *, wl_proxy_get_class, (struct wl_proxy *))
50SDL_WAYLAND_SYM(void, wl_proxy_set_queue, (struct wl_proxy *, struct wl_event_queue *))
51SDL_WAYLAND_SYM(void *, wl_proxy_create_wrapper, (void *))
52SDL_WAYLAND_SYM(void, wl_proxy_wrapper_destroy, (void *))
53SDL_WAYLAND_SYM(struct wl_display *, wl_display_connect, (const char *))
54SDL_WAYLAND_SYM(struct wl_display *, wl_display_connect_to_fd, (int))
55SDL_WAYLAND_SYM(void, wl_display_disconnect, (struct wl_display *))
56SDL_WAYLAND_SYM(int, wl_display_get_fd, (struct wl_display *))
57SDL_WAYLAND_SYM(int, wl_display_dispatch, (struct wl_display *))
58SDL_WAYLAND_SYM(int, wl_display_dispatch_queue, (struct wl_display *, struct wl_event_queue *))
59SDL_WAYLAND_SYM(int, wl_display_dispatch_queue_pending, (struct wl_display *, struct wl_event_queue *))
60SDL_WAYLAND_SYM(int, wl_display_dispatch_pending, (struct wl_display *))
61SDL_WAYLAND_SYM(int, wl_display_prepare_read, (struct wl_display *))
62SDL_WAYLAND_SYM(int, wl_display_prepare_read_queue, (struct wl_display *, struct wl_event_queue *))
63SDL_WAYLAND_SYM(int, wl_display_read_events, (struct wl_display *))
64SDL_WAYLAND_SYM(void, wl_display_cancel_read, (struct wl_display *))
65SDL_WAYLAND_SYM(int, wl_display_get_error, (struct wl_display *))
66SDL_WAYLAND_SYM(int, wl_display_flush, (struct wl_display *))
67SDL_WAYLAND_SYM(int, wl_display_roundtrip, (struct wl_display *))
68SDL_WAYLAND_SYM(struct wl_event_queue *, wl_display_create_queue, (struct wl_display *))
69SDL_WAYLAND_SYM(void, wl_event_queue_destroy, (struct wl_event_queue *))
70SDL_WAYLAND_SYM(void, wl_log_set_handler_client, (wl_log_func_t))
71SDL_WAYLAND_SYM(void, wl_list_init, (struct wl_list *))
72SDL_WAYLAND_SYM(void, wl_list_insert, (struct wl_list *, struct wl_list *) )
73SDL_WAYLAND_SYM(void, wl_list_remove, (struct wl_list *))
74SDL_WAYLAND_SYM(int, wl_list_length, (const struct wl_list *))
75SDL_WAYLAND_SYM(int, wl_list_empty, (const struct wl_list *))
76SDL_WAYLAND_SYM(void, wl_list_insert_list, (struct wl_list *, struct wl_list *))
77SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_constructor, (struct wl_proxy *, uint32_t opcode, const struct wl_interface *interface, ...))
78SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_constructor_versioned, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interface, uint32_t version, ...))
79SDL_WAYLAND_SYM(void, wl_proxy_set_tag, (struct wl_proxy *, const char * const *))
80SDL_WAYLAND_SYM(const char * const *, wl_proxy_get_tag, (struct wl_proxy *))
81
82#if SDL_WAYLAND_CHECK_VERSION(1, 20, 0)
83/* wayland-scanner 1.20 generates code that will call these, so these are
84 * non-optional when we are compiling against Wayland 1.20. We don't
85 * explicitly call them ourselves, though, so if we are only compiling
86 * against Wayland 1.18, they're unnecessary. */
87SDL_WAYLAND_SYM(struct wl_proxy*, wl_proxy_marshal_flags, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interfac, uint32_t version, uint32_t flags, ...))
88SDL_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))
89#endif
90
91#if 0 // TODO RECONNECT: See waylandvideo.c for more information!
92#if SDL_WAYLAND_CHECK_VERSION(broken, on, purpose)
93SDL_WAYLAND_SYM(int, wl_display_reconnect, (struct wl_display*))
94#endif
95#endif // 0
96
97SDL_WAYLAND_INTERFACE(wl_seat_interface)
98SDL_WAYLAND_INTERFACE(wl_surface_interface)
99SDL_WAYLAND_INTERFACE(wl_shm_pool_interface)
100SDL_WAYLAND_INTERFACE(wl_buffer_interface)
101SDL_WAYLAND_INTERFACE(wl_registry_interface)
102SDL_WAYLAND_INTERFACE(wl_region_interface)
103SDL_WAYLAND_INTERFACE(wl_pointer_interface)
104SDL_WAYLAND_INTERFACE(wl_keyboard_interface)
105SDL_WAYLAND_INTERFACE(wl_compositor_interface)
106SDL_WAYLAND_INTERFACE(wl_output_interface)
107SDL_WAYLAND_INTERFACE(wl_shm_interface)
108SDL_WAYLAND_INTERFACE(wl_data_device_interface)
109SDL_WAYLAND_INTERFACE(wl_data_source_interface)
110SDL_WAYLAND_INTERFACE(wl_data_offer_interface)
111SDL_WAYLAND_INTERFACE(wl_data_device_manager_interface)
112
113SDL_WAYLAND_MODULE(WAYLAND_EGL)
114SDL_WAYLAND_SYM(struct wl_egl_window *, wl_egl_window_create, (struct wl_surface *, int, int))
115SDL_WAYLAND_SYM(void, wl_egl_window_destroy, (struct wl_egl_window *))
116SDL_WAYLAND_SYM(void, wl_egl_window_resize, (struct wl_egl_window *, int, int, int, int))
117SDL_WAYLAND_SYM(void, wl_egl_window_get_attached_size, (struct wl_egl_window *, int *, int *))
118
119SDL_WAYLAND_MODULE(WAYLAND_CURSOR)
120SDL_WAYLAND_SYM(struct wl_cursor_theme *, wl_cursor_theme_load, (const char *, int , struct wl_shm *))
121SDL_WAYLAND_SYM(void, wl_cursor_theme_destroy, (struct wl_cursor_theme *))
122SDL_WAYLAND_SYM(struct wl_cursor *, wl_cursor_theme_get_cursor, (struct wl_cursor_theme *, const char *))
123SDL_WAYLAND_SYM(struct wl_buffer *, wl_cursor_image_get_buffer, (struct wl_cursor_image *))
124SDL_WAYLAND_SYM(int, wl_cursor_frame, (struct wl_cursor *, uint32_t))
125
126SDL_WAYLAND_MODULE(WAYLAND_XKB)
127SDL_WAYLAND_SYM(int, xkb_state_key_get_syms, (struct xkb_state *, xkb_keycode_t, const xkb_keysym_t **))
128SDL_WAYLAND_SYM(int, xkb_keysym_to_utf8, (xkb_keysym_t, char *, size_t) )
129SDL_WAYLAND_SYM(struct xkb_keymap *, xkb_keymap_new_from_string, (struct xkb_context *, const char *, enum xkb_keymap_format, enum xkb_keymap_compile_flags))
130SDL_WAYLAND_SYM(struct xkb_state *, xkb_state_new, (struct xkb_keymap *) )
131SDL_WAYLAND_SYM(int, xkb_keymap_key_repeats, (struct xkb_keymap *keymap, xkb_keycode_t key) )
132SDL_WAYLAND_SYM(void, xkb_keymap_unref, (struct xkb_keymap *) )
133SDL_WAYLAND_SYM(void, xkb_state_unref, (struct xkb_state *) )
134SDL_WAYLAND_SYM(void, xkb_context_unref, (struct xkb_context *) )
135SDL_WAYLAND_SYM(struct xkb_context *, xkb_context_new, (enum xkb_context_flags flags) )
136SDL_WAYLAND_SYM(enum xkb_state_component, xkb_state_update_mask, (struct xkb_state *state,\
137 xkb_mod_mask_t depressed_mods,\
138 xkb_mod_mask_t latched_mods,\
139 xkb_mod_mask_t locked_mods,\
140 xkb_layout_index_t depressed_layout,\
141 xkb_layout_index_t latched_layout,\
142 xkb_layout_index_t locked_layout) )
143SDL_WAYLAND_SYM(struct xkb_compose_table *, xkb_compose_table_new_from_locale, (struct xkb_context *,\
144 const char *locale, enum xkb_compose_compile_flags) )
145SDL_WAYLAND_SYM(void, xkb_compose_state_reset, (struct xkb_compose_state *) )
146SDL_WAYLAND_SYM(void, xkb_compose_table_unref, (struct xkb_compose_table *) )
147SDL_WAYLAND_SYM(struct xkb_compose_state *, xkb_compose_state_new, (struct xkb_compose_table *, enum xkb_compose_state_flags) )
148SDL_WAYLAND_SYM(void, xkb_compose_state_unref, (struct xkb_compose_state *) )
149SDL_WAYLAND_SYM(enum xkb_compose_feed_result, xkb_compose_state_feed, (struct xkb_compose_state *, xkb_keysym_t) )
150SDL_WAYLAND_SYM(enum xkb_compose_status, xkb_compose_state_get_status, (struct xkb_compose_state *) )
151SDL_WAYLAND_SYM(xkb_keysym_t, xkb_compose_state_get_one_sym, (struct xkb_compose_state *) )
152SDL_WAYLAND_SYM(void, xkb_keymap_key_for_each, (struct xkb_keymap *, xkb_keymap_key_iter_t, void*) )
153SDL_WAYLAND_SYM(int, xkb_keymap_key_get_syms_by_level, (struct xkb_keymap *,
154 xkb_keycode_t,
155 xkb_layout_index_t,
156 xkb_layout_index_t,
157 const xkb_keysym_t **) )
158SDL_WAYLAND_SYM(uint32_t, xkb_keysym_to_utf32, (xkb_keysym_t) )
159SDL_WAYLAND_SYM(uint32_t, xkb_keymap_mod_get_index, (struct xkb_keymap *,
160 const char *) )
161SDL_WAYLAND_SYM(const char *, xkb_keymap_layout_get_name, (struct xkb_keymap*, xkb_layout_index_t))
162
163#ifdef HAVE_LIBDECOR_H
164SDL_WAYLAND_MODULE(WAYLAND_LIBDECOR)
165SDL_WAYLAND_SYM(void, libdecor_unref, (struct libdecor *))
166SDL_WAYLAND_SYM(struct libdecor *, libdecor_new, (struct wl_display *, struct libdecor_interface *))
167SDL_WAYLAND_SYM(struct libdecor_frame *, libdecor_decorate, (struct libdecor *,\
168 struct wl_surface *,\
169 struct libdecor_frame_interface *,\
170 void *))
171SDL_WAYLAND_SYM(void, libdecor_frame_unref, (struct libdecor_frame *))
172SDL_WAYLAND_SYM(void, libdecor_frame_set_title, (struct libdecor_frame *, const char *))
173SDL_WAYLAND_SYM(void, libdecor_frame_set_app_id, (struct libdecor_frame *, const char *))
174SDL_WAYLAND_SYM(void, libdecor_frame_set_max_content_size, (struct libdecor_frame *frame,\
175 int content_width,\
176 int content_height))
177SDL_WAYLAND_SYM(void, libdecor_frame_set_min_content_size, (struct libdecor_frame *frame,\
178 int content_width,\
179 int content_height))
180SDL_WAYLAND_SYM(void, libdecor_frame_resize, (struct libdecor_frame *,\
181 struct wl_seat *,\
182 uint32_t,\
183 enum libdecor_resize_edge))
184SDL_WAYLAND_SYM(void, libdecor_frame_move, (struct libdecor_frame *,\
185 struct wl_seat *,\
186 uint32_t))
187SDL_WAYLAND_SYM(void, libdecor_frame_commit, (struct libdecor_frame *,\
188 struct libdecor_state *,\
189 struct libdecor_configuration *))
190SDL_WAYLAND_SYM(void, libdecor_frame_set_minimized, (struct libdecor_frame *))
191SDL_WAYLAND_SYM(void, libdecor_frame_set_maximized, (struct libdecor_frame *))
192SDL_WAYLAND_SYM(void, libdecor_frame_unset_maximized, (struct libdecor_frame *))
193SDL_WAYLAND_SYM(void, libdecor_frame_set_fullscreen, (struct libdecor_frame *, struct wl_output *))
194SDL_WAYLAND_SYM(void, libdecor_frame_unset_fullscreen, (struct libdecor_frame *))
195SDL_WAYLAND_SYM(void, libdecor_frame_set_capabilities, (struct libdecor_frame *, \
196 enum libdecor_capabilities))
197SDL_WAYLAND_SYM(void, libdecor_frame_unset_capabilities, (struct libdecor_frame *, \
198 enum libdecor_capabilities))
199SDL_WAYLAND_SYM(bool, libdecor_frame_has_capability, (struct libdecor_frame *, \
200 enum libdecor_capabilities))
201SDL_WAYLAND_SYM(void, libdecor_frame_set_visibility, (struct libdecor_frame *, bool))
202SDL_WAYLAND_SYM(bool, libdecor_frame_is_visible, (struct libdecor_frame *))
203SDL_WAYLAND_SYM(bool, libdecor_frame_is_floating, (struct libdecor_frame *))
204SDL_WAYLAND_SYM(void, libdecor_frame_set_parent, (struct libdecor_frame *,\
205 struct libdecor_frame *))
206SDL_WAYLAND_SYM(void, libdecor_frame_show_window_menu, (struct libdecor_frame *, struct wl_seat *, uint32_t, int, int))
207SDL_WAYLAND_SYM(struct xdg_surface *, libdecor_frame_get_xdg_surface, (struct libdecor_frame *))
208SDL_WAYLAND_SYM(struct xdg_toplevel *, libdecor_frame_get_xdg_toplevel, (struct libdecor_frame *))
209SDL_WAYLAND_SYM(void, libdecor_frame_translate_coordinate, (struct libdecor_frame *, int, int, int *, int *))
210SDL_WAYLAND_SYM(void, libdecor_frame_map, (struct libdecor_frame *))
211SDL_WAYLAND_SYM(struct libdecor_state *, libdecor_state_new, (int, int))
212SDL_WAYLAND_SYM(void, libdecor_state_free, (struct libdecor_state *))
213SDL_WAYLAND_SYM(bool, libdecor_configuration_get_content_size, (struct libdecor_configuration *,\
214 struct libdecor_frame *,\
215 int *,\
216 int *))
217SDL_WAYLAND_SYM(bool, libdecor_configuration_get_window_state, (struct libdecor_configuration *,\
218 enum libdecor_window_state *))
219SDL_WAYLAND_SYM(int, libdecor_dispatch, (struct libdecor *, int))
220
221#if defined(SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR) || SDL_LIBDECOR_CHECK_VERSION(0, 2, 0)
222// Only found in libdecor 0.1.1 or higher, so failure to load them is not fatal.
223SDL_WAYLAND_SYM_OPT(void, libdecor_frame_get_min_content_size, (const struct libdecor_frame *,\
224 int *,\
225 int *))
226SDL_WAYLAND_SYM_OPT(void, libdecor_frame_get_max_content_size, (const struct libdecor_frame *,\
227 int *,\
228 int *))
229#endif
230
231#if defined(SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR) || SDL_LIBDECOR_CHECK_VERSION(0, 3, 0)
232SDL_WAYLAND_SYM_OPT(enum libdecor_wm_capabilities, libdecor_frame_get_wm_capabilities, (struct libdecor_frame *))
233#endif
234
235#endif
236
237#undef SDL_WAYLAND_MODULE
238#undef SDL_WAYLAND_SYM
239#undef SDL_WAYLAND_SYM_OPT
240#undef SDL_WAYLAND_INTERFACE
241
242/* *INDENT-ON* */ // clang-format on
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvideo.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvideo.c
new file mode 100644
index 0000000..4301953
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvideo.c
@@ -0,0 +1,1692 @@
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 "../../core/linux/SDL_system_theme.h"
27#include "../../events/SDL_events_c.h"
28
29#include "SDL_waylandclipboard.h"
30#include "SDL_waylandcolor.h"
31#include "SDL_waylandevents_c.h"
32#include "SDL_waylandkeyboard.h"
33#include "SDL_waylandmessagebox.h"
34#include "SDL_waylandmouse.h"
35#include "SDL_waylandopengles.h"
36#include "SDL_waylandvideo.h"
37#include "SDL_waylandvulkan.h"
38#include "SDL_waylandwindow.h"
39
40#include <fcntl.h>
41#include <sys/types.h>
42#include <unistd.h>
43#include <xkbcommon/xkbcommon.h>
44
45#include <wayland-util.h>
46
47#include "alpha-modifier-v1-client-protocol.h"
48#include "cursor-shape-v1-client-protocol.h"
49#include "fractional-scale-v1-client-protocol.h"
50#include "frog-color-management-v1-client-protocol.h"
51#include "idle-inhibit-unstable-v1-client-protocol.h"
52#include "input-timestamps-unstable-v1-client-protocol.h"
53#include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
54#include "pointer-constraints-unstable-v1-client-protocol.h"
55#include "primary-selection-unstable-v1-client-protocol.h"
56#include "relative-pointer-unstable-v1-client-protocol.h"
57#include "tablet-v2-client-protocol.h"
58#include "text-input-unstable-v3-client-protocol.h"
59#include "viewporter-client-protocol.h"
60#include "xdg-activation-v1-client-protocol.h"
61#include "xdg-decoration-unstable-v1-client-protocol.h"
62#include "xdg-dialog-v1-client-protocol.h"
63#include "xdg-foreign-unstable-v2-client-protocol.h"
64#include "xdg-output-unstable-v1-client-protocol.h"
65#include "xdg-shell-client-protocol.h"
66#include "xdg-toplevel-icon-v1-client-protocol.h"
67#include "color-management-v1-client-protocol.h"
68
69#ifdef HAVE_LIBDECOR_H
70#include <libdecor.h>
71#endif
72
73#define WAYLANDVID_DRIVER_NAME "wayland"
74
75// Clamp certain core protocol versions on older versions of libwayland.
76#if SDL_WAYLAND_CHECK_VERSION(1, 22, 0)
77#define SDL_WL_COMPOSITOR_VERSION 6
78#else
79#define SDL_WL_COMPOSITOR_VERSION 4
80#endif
81
82#if SDL_WAYLAND_CHECK_VERSION(1, 22, 0)
83#define SDL_WL_SEAT_VERSION 9
84#elif SDL_WAYLAND_CHECK_VERSION(1, 21, 0)
85#define SDL_WL_SEAT_VERSION 8
86#else
87#define SDL_WL_SEAT_VERSION 5
88#endif
89
90#if SDL_WAYLAND_CHECK_VERSION(1, 20, 0)
91#define SDL_WL_OUTPUT_VERSION 4
92#else
93#define SDL_WL_OUTPUT_VERSION 3
94#endif
95
96#ifdef SDL_USE_LIBDBUS
97#include "../../core/linux/SDL_dbus.h"
98
99#define DISPLAY_INFO_NODE "org.gnome.Mutter.DisplayConfig"
100#define DISPLAY_INFO_PATH "/org/gnome/Mutter/DisplayConfig"
101#define DISPLAY_INFO_METHOD "GetCurrentState"
102#endif
103
104/* GNOME doesn't expose displays in any particular order, but we can find the
105 * primary display and its logical coordinates via a DBus method.
106 */
107static bool Wayland_GetGNOMEPrimaryDisplayCoordinates(int *x, int *y)
108{
109#ifdef SDL_USE_LIBDBUS
110 SDL_DBusContext *dbus = SDL_DBus_GetContext();
111 if (dbus == NULL) {
112 return false;
113 }
114 DBusMessage *reply = NULL;
115 DBusMessageIter iter[3];
116 DBusMessage *msg = dbus->message_new_method_call(DISPLAY_INFO_NODE,
117 DISPLAY_INFO_PATH,
118 DISPLAY_INFO_NODE,
119 DISPLAY_INFO_METHOD);
120
121 if (msg) {
122 reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, NULL);
123 dbus->message_unref(msg);
124 }
125
126 if (reply) {
127 // Serial (don't care)
128 dbus->message_iter_init(reply, &iter[0]);
129 if (dbus->message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_UINT32) {
130 goto error;
131 }
132
133 // Physical monitor array (don't care)
134 dbus->message_iter_next(&iter[0]);
135 if (dbus->message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_ARRAY) {
136 goto error;
137 }
138
139 // Logical monitor array of structs
140 dbus->message_iter_next(&iter[0]);
141 if (dbus->message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_ARRAY) {
142 goto error;
143 }
144
145 // First logical monitor struct
146 dbus->message_iter_recurse(&iter[0], &iter[1]);
147 if (dbus->message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_STRUCT) {
148 goto error;
149 }
150
151 do {
152 int logical_x, logical_y;
153 dbus_bool_t primary;
154
155 // Logical X
156 dbus->message_iter_recurse(&iter[1], &iter[2]);
157 if (dbus->message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_INT32) {
158 goto error;
159 }
160 dbus->message_iter_get_basic(&iter[2], &logical_x);
161
162 // Logical Y
163 dbus->message_iter_next(&iter[2]);
164 if (dbus->message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_INT32) {
165 goto error;
166 }
167 dbus->message_iter_get_basic(&iter[2], &logical_y);
168
169 // Scale (don't care)
170 dbus->message_iter_next(&iter[2]);
171 if (dbus->message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_DOUBLE) {
172 goto error;
173 }
174
175 // Transform (don't care)
176 dbus->message_iter_next(&iter[2]);
177 if (dbus->message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_UINT32) {
178 goto error;
179 }
180
181 // Primary display boolean
182 dbus->message_iter_next(&iter[2]);
183 if (dbus->message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_BOOLEAN) {
184 goto error;
185 }
186 dbus->message_iter_get_basic(&iter[2], &primary);
187
188 if (primary) {
189 *x = logical_x;
190 *y = logical_y;
191
192 // We found the primary display: success.
193 dbus->message_unref(reply);
194 return true;
195 }
196 } while (dbus->message_iter_next(&iter[1]));
197 }
198
199error:
200 if (reply) {
201 dbus->message_unref(reply);
202 }
203#endif
204 return false;
205}
206
207// Sort the list of displays into a deterministic order
208static int SDLCALL Wayland_DisplayPositionCompare(const void *a, const void *b)
209{
210 const SDL_DisplayData *da = *(SDL_DisplayData **)a;
211 const SDL_DisplayData *db = *(SDL_DisplayData **)b;
212
213 const bool a_at_origin = da->x == 0 && da->y == 0;
214 const bool b_at_origin = db->x == 0 && db->y == 0;
215
216 // Sort the display at 0,0 to be beginning of the list, as that will be the fallback primary.
217 if (a_at_origin && !b_at_origin) {
218 return -1;
219 }
220 if (b_at_origin && !a_at_origin) {
221 return 1;
222 }
223 if (da->x < db->x) {
224 return -1;
225 }
226 if (da->x > db->x) {
227 return 1;
228 }
229 if (da->y < db->y) {
230 return -1;
231 }
232 if (da->y > db->y) {
233 return 1;
234 }
235
236 // If no position information is available, use the connector name.
237 if (da->wl_output_name && db->wl_output_name) {
238 return SDL_strcmp(da->wl_output_name, db->wl_output_name);
239 }
240
241 return 0;
242}
243
244/* Wayland doesn't have the native concept of a primary display, but there are clients that
245 * will base their resolution lists on, or automatically make themselves fullscreen on, the
246 * first listed output, which can lead to problems if the first listed output isn't
247 * necessarily the best display for this. This attempts to find a primary display, first by
248 * querying the GNOME DBus property, then trying to determine the 'best' display if that fails.
249 * If all displays are equal, the one at position 0,0 will become the primary.
250 *
251 * The primary is determined by the following criteria, in order:
252 * - Landscape is preferred over portrait
253 * - The highest native resolution
254 * - A higher HDR range is preferred
255 * - Higher refresh is preferred (ignoring small differences)
256 * - Lower scale values are preferred (larger display)
257 */
258static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid)
259{
260 static const int REFRESH_DELTA = 4000;
261
262 // Query the DBus interface to see if the coordinates of the primary display are exposed.
263 int x, y;
264 if (Wayland_GetGNOMEPrimaryDisplayCoordinates(&x, &y)) {
265 for (int i = 0; i < vid->output_count; ++i) {
266 if (vid->output_list[i]->x == x && vid->output_list[i]->y == y) {
267 return i;
268 }
269 }
270 }
271
272 // Otherwise, choose the 'best' display.
273 int best_width = 0;
274 int best_height = 0;
275 double best_scale = 0.0;
276 float best_headroom = 0.0f;
277 int best_refresh = 0;
278 bool best_is_landscape = false;
279 int best_index = 0;
280
281 for (int i = 0; i < vid->output_count; ++i) {
282 const SDL_DisplayData *d = vid->output_list[i];
283 const bool is_landscape = d->orientation != SDL_ORIENTATION_PORTRAIT && d->orientation != SDL_ORIENTATION_PORTRAIT_FLIPPED;
284 bool have_new_best = false;
285
286 if (!best_is_landscape && is_landscape) { // Favor landscape over portrait displays.
287 have_new_best = true;
288 } else if (!best_is_landscape || is_landscape) { // Ignore portrait displays if a landscape was already found.
289 if (d->pixel_width > best_width || d->pixel_height > best_height) {
290 have_new_best = true;
291 } else if (d->pixel_width == best_width && d->pixel_height == best_height) {
292 if (d->HDR.HDR_headroom > best_headroom) { // Favor a higher HDR luminance range
293 have_new_best = true;
294 } else if (d->HDR.HDR_headroom == best_headroom) {
295 if (d->refresh - best_refresh > REFRESH_DELTA) { // Favor a higher refresh rate, but ignore small differences (e.g. 59.97 vs 60.1)
296 have_new_best = true;
297 } else if (d->scale_factor < best_scale && SDL_abs(d->refresh - best_refresh) <= REFRESH_DELTA) {
298 // Prefer a lower scale display if the difference in refresh rate is small.
299 have_new_best = true;
300 }
301 }
302 }
303 }
304
305 if (have_new_best) {
306 best_width = d->pixel_width;
307 best_height = d->pixel_height;
308 best_scale = d->scale_factor;
309 best_headroom = d->HDR.HDR_headroom;
310 best_refresh = d->refresh;
311 best_is_landscape = is_landscape;
312 best_index = i;
313 }
314 }
315
316 return best_index;
317}
318
319static void Wayland_SortOutputsByPriorityHint(SDL_VideoData *vid)
320{
321 const char *name_hint = SDL_GetHint(SDL_HINT_VIDEO_DISPLAY_PRIORITY);
322
323 if (name_hint) {
324 char *saveptr;
325 char *str = SDL_strdup(name_hint);
326 SDL_DisplayData **sorted_list = SDL_malloc(sizeof(SDL_DisplayData *) * vid->output_count);
327
328 if (str && sorted_list) {
329 int sorted_index = 0;
330
331 // Sort the requested displays to the front of the list.
332 const char *token = SDL_strtok_r(str, ",", &saveptr);
333 while (token) {
334 for (int i = 0; i < vid->output_count; ++i) {
335 SDL_DisplayData *d = vid->output_list[i];
336 if (d && d->wl_output_name && SDL_strcmp(token, d->wl_output_name) == 0) {
337 sorted_list[sorted_index++] = d;
338 vid->output_list[i] = NULL;
339 break;
340 }
341 }
342
343 token = SDL_strtok_r(NULL, ",", &saveptr);
344 }
345
346 // Append the remaining outputs to the end of the list.
347 for (int i = 0; i < vid->output_count; ++i) {
348 if (vid->output_list[i]) {
349 sorted_list[sorted_index++] = vid->output_list[i];
350 }
351 }
352
353 // Copy the sorted list to the output list.
354 SDL_memcpy(vid->output_list, sorted_list, sizeof(SDL_DisplayData *) * vid->output_count);
355 }
356
357 SDL_free(str);
358 SDL_free(sorted_list);
359 }
360}
361
362static void Wayland_SortOutputs(SDL_VideoData *vid)
363{
364 // Sort by position or connector name, so the order of outputs is deterministic.
365 SDL_qsort(vid->output_list, vid->output_count, sizeof(SDL_DisplayData *), Wayland_DisplayPositionCompare);
366
367 // Find a suitable primary display and move it to the front of the list.
368 const int primary_index = Wayland_GetPrimaryDisplay(vid);
369 if (primary_index) {
370 SDL_DisplayData *primary = vid->output_list[primary_index];
371 SDL_memmove(&vid->output_list[1], &vid->output_list[0], sizeof(SDL_DisplayData *) * primary_index);
372 vid->output_list[0] = primary;
373 }
374
375 // Apply the ordering hint, if specified.
376 Wayland_SortOutputsByPriorityHint(vid);
377}
378
379static void display_handle_done(void *data, struct wl_output *output);
380
381// Initialization/Query functions
382static bool Wayland_VideoInit(SDL_VideoDevice *_this);
383static bool Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect);
384static void Wayland_VideoQuit(SDL_VideoDevice *_this);
385
386static const char *SDL_WAYLAND_surface_tag = "sdl-window";
387static const char *SDL_WAYLAND_output_tag = "sdl-output";
388
389void SDL_WAYLAND_register_surface(struct wl_surface *surface)
390{
391 wl_proxy_set_tag((struct wl_proxy *)surface, &SDL_WAYLAND_surface_tag);
392}
393
394void SDL_WAYLAND_register_output(struct wl_output *output)
395{
396 wl_proxy_set_tag((struct wl_proxy *)output, &SDL_WAYLAND_output_tag);
397}
398
399bool SDL_WAYLAND_own_surface(struct wl_surface *surface)
400{
401 return wl_proxy_get_tag((struct wl_proxy *)surface) == &SDL_WAYLAND_surface_tag;
402}
403
404bool SDL_WAYLAND_own_output(struct wl_output *output)
405{
406 return wl_proxy_get_tag((struct wl_proxy *)output) == &SDL_WAYLAND_output_tag;
407}
408
409/* External surfaces may have their own user data attached, the modification of which
410 * can cause problems with external toolkits. Instead, external windows are kept in
411 * their own list, and a search is conducted to find a matching surface.
412 */
413static struct wl_list external_window_list;
414
415void Wayland_AddWindowDataToExternalList(SDL_WindowData *data)
416{
417 WAYLAND_wl_list_insert(&external_window_list, &data->external_window_list_link);
418}
419
420void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data)
421{
422 WAYLAND_wl_list_remove(&data->external_window_list_link);
423}
424
425SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface)
426{
427 if (SDL_WAYLAND_own_surface(surface)) {
428 return (SDL_WindowData *)wl_surface_get_user_data(surface);
429 } else if (!WAYLAND_wl_list_empty(&external_window_list)) {
430 SDL_WindowData *p;
431 wl_list_for_each (p, &external_window_list, external_window_list_link) {
432 if (p->surface == surface) {
433 return p;
434 }
435 }
436 }
437
438 return NULL;
439}
440
441static void Wayland_DeleteDevice(SDL_VideoDevice *device)
442{
443 SDL_VideoData *data = device->internal;
444 if (data->display && !data->display_externally_owned) {
445 WAYLAND_wl_display_flush(data->display);
446 WAYLAND_wl_display_disconnect(data->display);
447 SDL_ClearProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER);
448 }
449 if (device->wakeup_lock) {
450 SDL_DestroyMutex(device->wakeup_lock);
451 }
452 SDL_free(data);
453 SDL_free(device);
454 SDL_WAYLAND_UnloadSymbols();
455}
456
457typedef struct
458{
459 bool has_fifo_v1;
460} SDL_WaylandPreferredData;
461
462static void wayland_preferred_check_handle_global(void *data, struct wl_registry *registry, uint32_t id,
463 const char *interface, uint32_t version)
464{
465 SDL_WaylandPreferredData *d = data;
466
467 if (SDL_strcmp(interface, "wp_fifo_manager_v1") == 0) {
468 d->has_fifo_v1 = true;
469 }
470}
471
472static void wayland_preferred_check_remove_global(void *data, struct wl_registry *registry, uint32_t id)
473{
474 // No need to do anything here.
475}
476
477static const struct wl_registry_listener preferred_registry_listener = {
478 wayland_preferred_check_handle_global,
479 wayland_preferred_check_remove_global
480};
481
482static bool Wayland_IsPreferred(struct wl_display *display)
483{
484 struct wl_registry *registry = wl_display_get_registry(display);
485 SDL_WaylandPreferredData preferred_data = { 0 };
486
487 if (!registry) {
488 SDL_SetError("Failed to get the Wayland registry");
489 return false;
490 }
491
492 wl_registry_add_listener(registry, &preferred_registry_listener, &preferred_data);
493
494 WAYLAND_wl_display_roundtrip(display);
495
496 wl_registry_destroy(registry);
497
498 return preferred_data.has_fifo_v1;
499}
500
501static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols)
502{
503 SDL_VideoDevice *device;
504 SDL_VideoData *data;
505 struct SDL_WaylandInput *input;
506 struct wl_display *display = SDL_GetPointerProperty(SDL_GetGlobalProperties(),
507 SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, NULL);
508 bool display_is_external = !!display;
509
510 // Are we trying to connect to or are currently in a Wayland session?
511 if (!SDL_getenv("WAYLAND_DISPLAY")) {
512 const char *session = SDL_getenv("XDG_SESSION_TYPE");
513 if (session && SDL_strcasecmp(session, "wayland") != 0) {
514 return NULL;
515 }
516 }
517
518 if (!SDL_WAYLAND_LoadSymbols()) {
519 return NULL;
520 }
521
522 if (!display) {
523 display = WAYLAND_wl_display_connect(NULL);
524 if (!display) {
525 SDL_WAYLAND_UnloadSymbols();
526 return NULL;
527 }
528 }
529
530 /*
531 * If we are checking for preferred Wayland, then let's query for
532 * fifo-v1's existence, so we don't regress GPU-bound performance
533 * and frame-pacing by default due to swapchain starvation.
534 */
535 if (require_preferred_protocols && !Wayland_IsPreferred(display)) {
536 if (!display_is_external) {
537 WAYLAND_wl_display_disconnect(display);
538 }
539 SDL_WAYLAND_UnloadSymbols();
540 return NULL;
541 }
542
543 data = SDL_calloc(1, sizeof(*data));
544 if (!data) {
545 if (!display_is_external) {
546 WAYLAND_wl_display_disconnect(display);
547 }
548 SDL_WAYLAND_UnloadSymbols();
549 return NULL;
550 }
551
552 input = SDL_calloc(1, sizeof(*input));
553 if (!input) {
554 SDL_free(data);
555 if (!display_is_external) {
556 WAYLAND_wl_display_disconnect(display);
557 }
558 SDL_WAYLAND_UnloadSymbols();
559 return NULL;
560 }
561
562 input->display = data;
563 input->sx_w = wl_fixed_from_int(0);
564 input->sy_w = wl_fixed_from_int(0);
565 input->xkb.current_group = XKB_GROUP_INVALID;
566
567 data->initializing = true;
568 data->display = display;
569 data->input = input;
570 data->display_externally_owned = display_is_external;
571 data->scale_to_display_enabled = SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_SCALE_TO_DISPLAY, false);
572 WAYLAND_wl_list_init(&external_window_list);
573
574 // Initialize all variables that we clean on shutdown
575 device = SDL_calloc(1, sizeof(SDL_VideoDevice));
576 if (!device) {
577 SDL_free(input);
578 SDL_free(data);
579 if (!display_is_external) {
580 WAYLAND_wl_display_disconnect(display);
581 }
582 SDL_WAYLAND_UnloadSymbols();
583 return NULL;
584 }
585
586 if (!display_is_external) {
587 SDL_SetPointerProperty(SDL_GetGlobalProperties(),
588 SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, display);
589 }
590
591 device->internal = data;
592 device->wakeup_lock = SDL_CreateMutex();
593
594 // Set the function pointers
595 device->VideoInit = Wayland_VideoInit;
596 device->VideoQuit = Wayland_VideoQuit;
597 device->GetDisplayBounds = Wayland_GetDisplayBounds;
598 device->SuspendScreenSaver = Wayland_SuspendScreenSaver;
599
600 device->PumpEvents = Wayland_PumpEvents;
601 device->WaitEventTimeout = Wayland_WaitEventTimeout;
602 device->SendWakeupEvent = Wayland_SendWakeupEvent;
603
604#ifdef SDL_VIDEO_OPENGL_EGL
605 device->GL_SwapWindow = Wayland_GLES_SwapWindow;
606 device->GL_GetSwapInterval = Wayland_GLES_GetSwapInterval;
607 device->GL_SetSwapInterval = Wayland_GLES_SetSwapInterval;
608 device->GL_MakeCurrent = Wayland_GLES_MakeCurrent;
609 device->GL_CreateContext = Wayland_GLES_CreateContext;
610 device->GL_LoadLibrary = Wayland_GLES_LoadLibrary;
611 device->GL_UnloadLibrary = Wayland_GLES_UnloadLibrary;
612 device->GL_GetProcAddress = Wayland_GLES_GetProcAddress;
613 device->GL_DestroyContext = Wayland_GLES_DestroyContext;
614 device->GL_GetEGLSurface = Wayland_GLES_GetEGLSurface;
615#endif
616
617 device->CreateSDLWindow = Wayland_CreateWindow;
618 device->ShowWindow = Wayland_ShowWindow;
619 device->HideWindow = Wayland_HideWindow;
620 device->RaiseWindow = Wayland_RaiseWindow;
621 device->SetWindowFullscreen = Wayland_SetWindowFullscreen;
622 device->MaximizeWindow = Wayland_MaximizeWindow;
623 device->MinimizeWindow = Wayland_MinimizeWindow;
624 device->SetWindowMouseRect = Wayland_SetWindowMouseRect;
625 device->SetWindowMouseGrab = Wayland_SetWindowMouseGrab;
626 device->SetWindowKeyboardGrab = Wayland_SetWindowKeyboardGrab;
627 device->RestoreWindow = Wayland_RestoreWindow;
628 device->SetWindowBordered = Wayland_SetWindowBordered;
629 device->SetWindowResizable = Wayland_SetWindowResizable;
630 device->SetWindowPosition = Wayland_SetWindowPosition;
631 device->SetWindowSize = Wayland_SetWindowSize;
632 device->SetWindowMinimumSize = Wayland_SetWindowMinimumSize;
633 device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize;
634 device->SetWindowParent = Wayland_SetWindowParent;
635 device->SetWindowModal = Wayland_SetWindowModal;
636 device->SetWindowOpacity = Wayland_SetWindowOpacity;
637 device->SetWindowTitle = Wayland_SetWindowTitle;
638 device->SetWindowIcon = Wayland_SetWindowIcon;
639 device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels;
640 device->GetWindowContentScale = Wayland_GetWindowContentScale;
641 device->GetWindowICCProfile = Wayland_GetWindowICCProfile;
642 device->GetDisplayForWindow = Wayland_GetDisplayForWindow;
643 device->DestroyWindow = Wayland_DestroyWindow;
644 device->SetWindowHitTest = Wayland_SetWindowHitTest;
645 device->FlashWindow = Wayland_FlashWindow;
646 device->HasScreenKeyboardSupport = Wayland_HasScreenKeyboardSupport;
647 device->ShowWindowSystemMenu = Wayland_ShowWindowSystemMenu;
648 device->SyncWindow = Wayland_SyncWindow;
649
650#ifdef SDL_USE_LIBDBUS
651 if (SDL_SystemTheme_Init())
652 device->system_theme = SDL_SystemTheme_Get();
653#endif
654
655 device->GetTextMimeTypes = Wayland_GetTextMimeTypes;
656 device->SetClipboardData = Wayland_SetClipboardData;
657 device->GetClipboardData = Wayland_GetClipboardData;
658 device->HasClipboardData = Wayland_HasClipboardData;
659 device->StartTextInput = Wayland_StartTextInput;
660 device->StopTextInput = Wayland_StopTextInput;
661 device->UpdateTextInputArea = Wayland_UpdateTextInputArea;
662
663#ifdef SDL_VIDEO_VULKAN
664 device->Vulkan_LoadLibrary = Wayland_Vulkan_LoadLibrary;
665 device->Vulkan_UnloadLibrary = Wayland_Vulkan_UnloadLibrary;
666 device->Vulkan_GetInstanceExtensions = Wayland_Vulkan_GetInstanceExtensions;
667 device->Vulkan_CreateSurface = Wayland_Vulkan_CreateSurface;
668 device->Vulkan_DestroySurface = Wayland_Vulkan_DestroySurface;
669 device->Vulkan_GetPresentationSupport = Wayland_Vulkan_GetPresentationSupport;
670#endif
671
672 device->free = Wayland_DeleteDevice;
673
674 device->device_caps = VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED |
675 VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT |
676 VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS |
677 VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES |
678 VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS |
679 VIDEO_DEVICE_CAPS_SENDS_HDR_CHANGES;
680
681 return device;
682}
683
684static SDL_VideoDevice *Wayland_Preferred_CreateDevice(void)
685{
686 return Wayland_CreateDevice(true);
687}
688
689static SDL_VideoDevice *Wayland_Fallback_CreateDevice(void)
690{
691 return Wayland_CreateDevice(false);
692}
693
694VideoBootStrap Wayland_preferred_bootstrap = {
695 WAYLANDVID_DRIVER_NAME, "SDL Wayland video driver",
696 Wayland_Preferred_CreateDevice,
697 Wayland_ShowMessageBox,
698 true
699};
700
701VideoBootStrap Wayland_bootstrap = {
702 WAYLANDVID_DRIVER_NAME, "SDL Wayland video driver",
703 Wayland_Fallback_CreateDevice,
704 Wayland_ShowMessageBox,
705 false
706};
707
708static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output,
709 int32_t x, int32_t y)
710{
711 SDL_DisplayData *internal = (SDL_DisplayData *)data;
712
713 internal->x = x;
714 internal->y = y;
715 internal->has_logical_position = true;
716}
717
718static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output,
719 int32_t width, int32_t height)
720{
721 SDL_DisplayData *internal = (SDL_DisplayData *)data;
722
723 internal->logical_width = width;
724 internal->logical_height = height;
725 internal->has_logical_size = true;
726}
727
728static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
729{
730 SDL_DisplayData *internal = data;
731
732 /*
733 * xdg-output.done events are deprecated and only apply below version 3 of the protocol.
734 * A wl-output.done event will be emitted in version 3 or higher.
735 */
736 if (zxdg_output_v1_get_version(internal->xdg_output) < 3) {
737 display_handle_done(data, internal->output);
738 }
739}
740
741static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output,
742 const char *name)
743{
744 SDL_DisplayData *internal = (SDL_DisplayData *)data;
745
746 // Deprecated as of wl_output v4.
747 if (wl_output_get_version(internal->output) < WL_OUTPUT_NAME_SINCE_VERSION &&
748 internal->display == 0) {
749 SDL_free(internal->wl_output_name);
750 internal->wl_output_name = SDL_strdup(name);
751 }
752}
753
754static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output,
755 const char *description)
756{
757 SDL_DisplayData *internal = (SDL_DisplayData *)data;
758
759 // Deprecated as of wl_output v4.
760 if (wl_output_get_version(internal->output) < WL_OUTPUT_DESCRIPTION_SINCE_VERSION &&
761 internal->display == 0) {
762 // xdg-output descriptions, if available, supersede wl-output model names.
763 SDL_free(internal->placeholder.name);
764 internal->placeholder.name = SDL_strdup(description);
765 }
766}
767
768static const struct zxdg_output_v1_listener xdg_output_listener = {
769 xdg_output_handle_logical_position,
770 xdg_output_handle_logical_size,
771 xdg_output_handle_done,
772 xdg_output_handle_name,
773 xdg_output_handle_description,
774};
775
776static void AddEmulatedModes(SDL_DisplayData *dispdata, int native_width, int native_height)
777{
778 struct EmulatedMode
779 {
780 int w;
781 int h;
782 };
783
784 // Resolution lists courtesy of XWayland
785 const struct EmulatedMode mode_list[] = {
786 // 16:9 (1.77)
787 { 7680, 4320 },
788 { 6144, 3160 },
789 { 5120, 2880 },
790 { 4096, 2304 },
791 { 3840, 2160 },
792 { 3200, 1800 },
793 { 2880, 1620 },
794 { 2560, 1440 },
795 { 2048, 1152 },
796 { 1920, 1080 },
797 { 1600, 900 },
798 { 1368, 768 },
799 { 1280, 720 },
800 { 864, 486 },
801
802 // 16:10 (1.6)
803 { 2560, 1600 },
804 { 1920, 1200 },
805 { 1680, 1050 },
806 { 1440, 900 },
807 { 1280, 800 },
808
809 // 3:2 (1.5)
810 { 720, 480 },
811
812 // 4:3 (1.33)
813 { 2048, 1536 },
814 { 1920, 1440 },
815 { 1600, 1200 },
816 { 1440, 1080 },
817 { 1400, 1050 },
818 { 1280, 1024 },
819 { 1280, 960 },
820 { 1152, 864 },
821 { 1024, 768 },
822 { 800, 600 },
823 { 640, 480 }
824 };
825
826 int i;
827 SDL_DisplayMode mode;
828 SDL_VideoDisplay *dpy = dispdata->display ? SDL_GetVideoDisplay(dispdata->display) : &dispdata->placeholder;
829 const bool rot_90 = native_width < native_height; // Reverse width/height for portrait displays.
830
831 for (i = 0; i < SDL_arraysize(mode_list); ++i) {
832 SDL_zero(mode);
833 mode.format = dpy->desktop_mode.format;
834 mode.refresh_rate_numerator = dpy->desktop_mode.refresh_rate_numerator;
835 mode.refresh_rate_denominator = dpy->desktop_mode.refresh_rate_denominator;
836
837 if (rot_90) {
838 mode.w = mode_list[i].h;
839 mode.h = mode_list[i].w;
840 } else {
841 mode.w = mode_list[i].w;
842 mode.h = mode_list[i].h;
843 }
844
845 // Only add modes that are smaller than the native mode.
846 if ((mode.w < native_width && mode.h < native_height) ||
847 (mode.w < native_width && mode.h == native_height) ||
848 (mode.w == native_width && mode.h < native_height)) {
849 SDL_AddFullscreenDisplayMode(dpy, &mode);
850 }
851 }
852}
853
854static void display_handle_geometry(void *data,
855 struct wl_output *output,
856 int x, int y,
857 int physical_width,
858 int physical_height,
859 int subpixel,
860 const char *make,
861 const char *model,
862 int transform)
863
864{
865 SDL_DisplayData *internal = (SDL_DisplayData *)data;
866
867 // Apply the change from wl-output only if xdg-output is not supported
868 if (!internal->has_logical_position) {
869 internal->x = x;
870 internal->y = y;
871 }
872 internal->physical_width_mm = physical_width;
873 internal->physical_height_mm = physical_height;
874
875 // The model is only used for the output name if wl_output or xdg-output haven't provided a description.
876 if (internal->display == 0 && !internal->placeholder.name) {
877 internal->placeholder.name = SDL_strdup(model);
878 }
879
880 internal->transform = transform;
881#define TF_CASE(in, out) \
882 case WL_OUTPUT_TRANSFORM_##in: \
883 internal->orientation = SDL_ORIENTATION_##out; \
884 break;
885 if (internal->physical_width_mm >= internal->physical_height_mm) {
886 switch (transform) {
887 TF_CASE(NORMAL, LANDSCAPE)
888 TF_CASE(90, PORTRAIT)
889 TF_CASE(180, LANDSCAPE_FLIPPED)
890 TF_CASE(270, PORTRAIT_FLIPPED)
891 TF_CASE(FLIPPED, LANDSCAPE_FLIPPED)
892 TF_CASE(FLIPPED_90, PORTRAIT_FLIPPED)
893 TF_CASE(FLIPPED_180, LANDSCAPE)
894 TF_CASE(FLIPPED_270, PORTRAIT)
895 }
896 } else {
897 switch (transform) {
898 TF_CASE(NORMAL, PORTRAIT)
899 TF_CASE(90, LANDSCAPE)
900 TF_CASE(180, PORTRAIT_FLIPPED)
901 TF_CASE(270, LANDSCAPE_FLIPPED)
902 TF_CASE(FLIPPED, PORTRAIT_FLIPPED)
903 TF_CASE(FLIPPED_90, LANDSCAPE_FLIPPED)
904 TF_CASE(FLIPPED_180, PORTRAIT)
905 TF_CASE(FLIPPED_270, LANDSCAPE)
906 }
907 }
908#undef TF_CASE
909}
910
911static void display_handle_mode(void *data,
912 struct wl_output *output,
913 uint32_t flags,
914 int width,
915 int height,
916 int refresh)
917{
918 SDL_DisplayData *internal = (SDL_DisplayData *)data;
919
920 if (flags & WL_OUTPUT_MODE_CURRENT) {
921 internal->pixel_width = width;
922 internal->pixel_height = height;
923
924 /*
925 * Don't rotate this yet, wl-output coordinates are transformed in
926 * handle_done and xdg-output coordinates are pre-transformed.
927 */
928 if (!internal->has_logical_size) {
929 internal->logical_width = width;
930 internal->logical_height = height;
931 }
932
933 internal->refresh = refresh;
934 }
935}
936
937static void display_handle_done(void *data,
938 struct wl_output *output)
939{
940 const bool mode_emulation_enabled = SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_MODE_EMULATION, true);
941 SDL_DisplayData *internal = (SDL_DisplayData *)data;
942 SDL_VideoData *video = internal->videodata;
943 SDL_DisplayMode native_mode, desktop_mode;
944
945 /*
946 * When using xdg-output, two wl-output.done events will be emitted:
947 * one at the completion of wl-display and one at the completion of xdg-output.
948 *
949 * All required events must be received before proceeding.
950 */
951 const int event_await_count = 1 + (internal->xdg_output != NULL);
952
953 internal->wl_output_done_count = SDL_min(internal->wl_output_done_count + 1, event_await_count + 1);
954
955 if (internal->wl_output_done_count < event_await_count) {
956 return;
957 }
958
959 // If the display was already created, reset and rebuild the mode list.
960 SDL_VideoDisplay *dpy = SDL_GetVideoDisplay(internal->display);
961 if (dpy) {
962 SDL_ResetFullscreenDisplayModes(dpy);
963 }
964
965 // The native display resolution
966 SDL_zero(native_mode);
967 native_mode.format = SDL_PIXELFORMAT_XRGB8888;
968
969 // Transform the pixel values, if necessary.
970 if (internal->transform & WL_OUTPUT_TRANSFORM_90) {
971 native_mode.w = internal->pixel_height;
972 native_mode.h = internal->pixel_width;
973 } else {
974 native_mode.w = internal->pixel_width;
975 native_mode.h = internal->pixel_height;
976 }
977 native_mode.refresh_rate_numerator = internal->refresh;
978 native_mode.refresh_rate_denominator = 1000;
979
980 if (internal->has_logical_size) { // If xdg-output is present...
981 if (native_mode.w != internal->logical_width || native_mode.h != internal->logical_height) {
982 // ...and the compositor scales the logical viewport...
983 if (video->viewporter) {
984 // ...and viewports are supported, calculate the true scale of the output.
985 internal->scale_factor = (double)native_mode.w / (double)internal->logical_width;
986 } else {
987 // ...otherwise, the 'native' pixel values are a multiple of the logical screen size.
988 internal->pixel_width = internal->logical_width * (int)internal->scale_factor;
989 internal->pixel_height = internal->logical_height * (int)internal->scale_factor;
990 }
991 } else {
992 /* ...and the output viewport is not scaled in the global compositing
993 * space, the output dimensions need to be divided by the scale factor.
994 */
995 internal->logical_width /= (int)internal->scale_factor;
996 internal->logical_height /= (int)internal->scale_factor;
997 }
998 } else {
999 /* Calculate the points from the pixel values, if xdg-output isn't present.
1000 * Use the native mode pixel values since they are pre-transformed.
1001 */
1002 internal->logical_width = native_mode.w / (int)internal->scale_factor;
1003 internal->logical_height = native_mode.h / (int)internal->scale_factor;
1004 }
1005
1006 // The scaled desktop mode
1007 SDL_zero(desktop_mode);
1008 desktop_mode.format = SDL_PIXELFORMAT_XRGB8888;
1009
1010 if (!video->scale_to_display_enabled) {
1011 desktop_mode.w = internal->logical_width;
1012 desktop_mode.h = internal->logical_height;
1013 desktop_mode.pixel_density = (float)internal->scale_factor;
1014 } else {
1015 desktop_mode.w = native_mode.w;
1016 desktop_mode.h = native_mode.h;
1017 desktop_mode.pixel_density = 1.0f;
1018 }
1019
1020 desktop_mode.refresh_rate_numerator = internal->refresh;
1021 desktop_mode.refresh_rate_denominator = 1000;
1022
1023 if (internal->display > 0) {
1024 dpy = SDL_GetVideoDisplay(internal->display);
1025 } else {
1026 dpy = &internal->placeholder;
1027 }
1028
1029 if (video->scale_to_display_enabled) {
1030 SDL_SetDisplayContentScale(dpy, (float)internal->scale_factor);
1031 }
1032
1033 // Set the desktop display mode.
1034 SDL_SetDesktopDisplayMode(dpy, &desktop_mode);
1035
1036 // Expose the unscaled, native resolution if the scale is 1.0 or viewports are available...
1037 if (internal->scale_factor == 1.0 || video->viewporter) {
1038 SDL_AddFullscreenDisplayMode(dpy, &native_mode);
1039 if (native_mode.w != desktop_mode.w ||
1040 native_mode.h != desktop_mode.h) {
1041 SDL_AddFullscreenDisplayMode(dpy, &desktop_mode);
1042 }
1043 } else {
1044 // ...otherwise expose the integer scaled variants of the desktop resolution down to 1.
1045 int i;
1046
1047 desktop_mode.pixel_density = 1.0f;
1048
1049 for (i = (int)internal->scale_factor; i > 0; --i) {
1050 desktop_mode.w = internal->logical_width * i;
1051 desktop_mode.h = internal->logical_height * i;
1052 SDL_AddFullscreenDisplayMode(dpy, &desktop_mode);
1053 }
1054 }
1055
1056 // Add emulated modes if wp_viewporter is supported and mode emulation is enabled.
1057 if (video->viewporter && mode_emulation_enabled) {
1058 // The transformed display pixel width/height must be used here.
1059 AddEmulatedModes(internal, native_mode.w, native_mode.h);
1060 }
1061
1062 SDL_SetDisplayHDRProperties(dpy, &internal->HDR);
1063
1064 if (internal->display == 0) {
1065 // First time getting display info, initialize the VideoDisplay
1066 if (internal->physical_width_mm >= internal->physical_height_mm) {
1067 internal->placeholder.natural_orientation = SDL_ORIENTATION_LANDSCAPE;
1068 } else {
1069 internal->placeholder.natural_orientation = SDL_ORIENTATION_PORTRAIT;
1070 }
1071 internal->placeholder.current_orientation = internal->orientation;
1072 internal->placeholder.internal = internal;
1073
1074 // During initialization, the displays will be added after enumeration is complete.
1075 if (!video->initializing) {
1076 internal->display = SDL_AddVideoDisplay(&internal->placeholder, true);
1077 SDL_free(internal->placeholder.name);
1078 SDL_zero(internal->placeholder);
1079 }
1080 } else {
1081 SDL_SendDisplayEvent(dpy, SDL_EVENT_DISPLAY_ORIENTATION, internal->orientation, 0);
1082 }
1083}
1084
1085static void display_handle_scale(void *data,
1086 struct wl_output *output,
1087 int32_t factor)
1088{
1089 SDL_DisplayData *internal = (SDL_DisplayData *)data;
1090 internal->scale_factor = factor;
1091}
1092
1093static void display_handle_name(void *data, struct wl_output *wl_output, const char *name)
1094{
1095 SDL_DisplayData *internal = (SDL_DisplayData *)data;
1096
1097 SDL_free(internal->wl_output_name);
1098 internal->wl_output_name = SDL_strdup(name);
1099}
1100
1101static void display_handle_description(void *data, struct wl_output *wl_output, const char *description)
1102{
1103 SDL_DisplayData *internal = (SDL_DisplayData *)data;
1104
1105 if (internal->display == 0) {
1106 // The description, if available, supersedes the model name.
1107 SDL_free(internal->placeholder.name);
1108 internal->placeholder.name = SDL_strdup(description);
1109 }
1110}
1111
1112static const struct wl_output_listener output_listener = {
1113 display_handle_geometry, // Version 1
1114 display_handle_mode, // Version 1
1115 display_handle_done, // Version 2
1116 display_handle_scale, // Version 2
1117 display_handle_name, // Version 4
1118 display_handle_description // Version 4
1119};
1120
1121static void handle_output_image_description_changed(void *data,
1122 struct wp_color_management_output_v1 *wp_color_management_output_v1)
1123{
1124 SDL_DisplayData *display = (SDL_DisplayData *)data;
1125 // wl_display.done is called after this event, so the display HDR status will be updated there.
1126 Wayland_GetColorInfoForOutput(display, false);
1127}
1128
1129static const struct wp_color_management_output_v1_listener wp_color_management_output_listener = {
1130 handle_output_image_description_changed
1131};
1132
1133static bool Wayland_add_display(SDL_VideoData *d, uint32_t id, uint32_t version)
1134{
1135 struct wl_output *output;
1136 SDL_DisplayData *data;
1137
1138 output = wl_registry_bind(d->registry, id, &wl_output_interface, version);
1139 if (!output) {
1140 return SDL_SetError("Failed to retrieve output.");
1141 }
1142 data = (SDL_DisplayData *)SDL_calloc(1, sizeof(*data));
1143 data->videodata = d;
1144 data->output = output;
1145 data->registry_id = id;
1146 data->scale_factor = 1.0f;
1147
1148 wl_output_add_listener(output, &output_listener, data);
1149 SDL_WAYLAND_register_output(output);
1150
1151 // Keep a list of outputs for sorting and deferred protocol initialization.
1152 if (d->output_count == d->output_max) {
1153 d->output_max += 4;
1154 d->output_list = SDL_realloc(d->output_list, sizeof(SDL_DisplayData *) * d->output_max);
1155 }
1156 d->output_list[d->output_count++] = data;
1157
1158 if (data->videodata->xdg_output_manager) {
1159 data->xdg_output = zxdg_output_manager_v1_get_xdg_output(data->videodata->xdg_output_manager, output);
1160 zxdg_output_v1_add_listener(data->xdg_output, &xdg_output_listener, data);
1161 }
1162 if (data->videodata->wp_color_manager_v1) {
1163 data->wp_color_management_output = wp_color_manager_v1_get_output(data->videodata->wp_color_manager_v1, output);
1164 wp_color_management_output_v1_add_listener(data->wp_color_management_output, &wp_color_management_output_listener, data);
1165 Wayland_GetColorInfoForOutput(data, true);
1166 }
1167 return true;
1168}
1169
1170static void Wayland_free_display(SDL_VideoDisplay *display, bool send_event)
1171{
1172 if (display) {
1173 SDL_DisplayData *display_data = display->internal;
1174
1175 /* A preceding surface leave event is not guaranteed when an output is removed,
1176 * so ensure that no window continues to hold a reference to a removed output.
1177 */
1178 for (SDL_Window *window = SDL_GetVideoDevice()->windows; window; window = window->next) {
1179 Wayland_RemoveOutputFromWindow(window->internal, display_data);
1180 }
1181
1182 SDL_free(display_data->wl_output_name);
1183
1184 if (display_data->wp_color_management_output) {
1185 Wayland_FreeColorInfoState(display_data->color_info_state);
1186 wp_color_management_output_v1_destroy(display_data->wp_color_management_output);
1187 }
1188
1189 if (display_data->xdg_output) {
1190 zxdg_output_v1_destroy(display_data->xdg_output);
1191 }
1192
1193 if (wl_output_get_version(display_data->output) >= WL_OUTPUT_RELEASE_SINCE_VERSION) {
1194 wl_output_release(display_data->output);
1195 } else {
1196 wl_output_destroy(display_data->output);
1197 }
1198
1199 SDL_DelVideoDisplay(display->id, send_event);
1200 }
1201}
1202
1203static void Wayland_FinalizeDisplays(SDL_VideoData *vid)
1204{
1205 Wayland_SortOutputs(vid);
1206 for(int i = 0; i < vid->output_count; ++i) {
1207 SDL_DisplayData *d = vid->output_list[i];
1208 d->display = SDL_AddVideoDisplay(&d->placeholder, false);
1209 SDL_free(d->placeholder.name);
1210 SDL_zero(d->placeholder);
1211 }
1212}
1213
1214static void Wayland_init_xdg_output(SDL_VideoData *d)
1215{
1216 for (int i = 0; i < d->output_count; ++i) {
1217 SDL_DisplayData *disp = d->output_list[i];
1218 disp->xdg_output = zxdg_output_manager_v1_get_xdg_output(disp->videodata->xdg_output_manager, disp->output);
1219 zxdg_output_v1_add_listener(disp->xdg_output, &xdg_output_listener, disp);
1220 }
1221}
1222
1223static void Wayland_InitColorManager(SDL_VideoData *d)
1224{
1225 for (int i = 0; i < d->output_count; ++i) {
1226 SDL_DisplayData *disp = d->output_list[i];
1227 disp->wp_color_management_output = wp_color_manager_v1_get_output(disp->videodata->wp_color_manager_v1, disp->output);
1228 wp_color_management_output_v1_add_listener(disp->wp_color_management_output, &wp_color_management_output_listener, disp);
1229 Wayland_GetColorInfoForOutput(disp, true);
1230 }
1231}
1232
1233static void handle_ping_xdg_wm_base(void *data, struct xdg_wm_base *xdg, uint32_t serial)
1234{
1235 xdg_wm_base_pong(xdg, serial);
1236}
1237
1238static const struct xdg_wm_base_listener shell_listener_xdg = {
1239 handle_ping_xdg_wm_base
1240};
1241
1242#ifdef HAVE_LIBDECOR_H
1243static void libdecor_error(struct libdecor *context,
1244 enum libdecor_error error,
1245 const char *message)
1246{
1247 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "libdecor error (%d): %s", error, message);
1248}
1249
1250static struct libdecor_interface libdecor_interface = {
1251 libdecor_error,
1252};
1253#endif
1254
1255static void display_handle_global(void *data, struct wl_registry *registry, uint32_t id,
1256 const char *interface, uint32_t version)
1257{
1258 SDL_VideoData *d = data;
1259
1260 // printf("WAYLAND INTERFACE: %s\n", interface);
1261
1262 if (SDL_strcmp(interface, "wl_compositor") == 0) {
1263 d->compositor = wl_registry_bind(d->registry, id, &wl_compositor_interface, SDL_min(SDL_WL_COMPOSITOR_VERSION, version));
1264 } else if (SDL_strcmp(interface, "wl_output") == 0) {
1265 Wayland_add_display(d, id, SDL_min(version, SDL_WL_OUTPUT_VERSION));
1266 } else if (SDL_strcmp(interface, "wl_seat") == 0) {
1267 d->input->seat = wl_registry_bind(d->registry, id, &wl_seat_interface, SDL_min(SDL_WL_SEAT_VERSION, version));
1268 Wayland_input_initialize_seat(d);
1269 } else if (SDL_strcmp(interface, "xdg_wm_base") == 0) {
1270 d->shell.xdg = wl_registry_bind(d->registry, id, &xdg_wm_base_interface, SDL_min(version, 6));
1271 xdg_wm_base_add_listener(d->shell.xdg, &shell_listener_xdg, NULL);
1272 } else if (SDL_strcmp(interface, "wl_shm") == 0) {
1273 d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
1274 } else if (SDL_strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) {
1275 d->relative_pointer_manager = wl_registry_bind(d->registry, id, &zwp_relative_pointer_manager_v1_interface, 1);
1276 Wayland_input_init_relative_pointer(d);
1277 } else if (SDL_strcmp(interface, "zwp_pointer_constraints_v1") == 0) {
1278 d->pointer_constraints = wl_registry_bind(d->registry, id, &zwp_pointer_constraints_v1_interface, 1);
1279 } else if (SDL_strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0) {
1280 d->key_inhibitor_manager = wl_registry_bind(d->registry, id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
1281 } else if (SDL_strcmp(interface, "zwp_idle_inhibit_manager_v1") == 0) {
1282 d->idle_inhibit_manager = wl_registry_bind(d->registry, id, &zwp_idle_inhibit_manager_v1_interface, 1);
1283 } else if (SDL_strcmp(interface, "xdg_activation_v1") == 0) {
1284 d->activation_manager = wl_registry_bind(d->registry, id, &xdg_activation_v1_interface, 1);
1285 } else if (SDL_strcmp(interface, "zwp_text_input_manager_v3") == 0) {
1286 Wayland_create_text_input_manager(d, id);
1287 } else if (SDL_strcmp(interface, "wl_data_device_manager") == 0) {
1288 d->data_device_manager = wl_registry_bind(d->registry, id, &wl_data_device_manager_interface, SDL_min(3, version));
1289 Wayland_create_data_device(d);
1290 } else if (SDL_strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0) {
1291 d->primary_selection_device_manager = wl_registry_bind(d->registry, id, &zwp_primary_selection_device_manager_v1_interface, 1);
1292 Wayland_create_primary_selection_device(d);
1293 } else if (SDL_strcmp(interface, "zxdg_decoration_manager_v1") == 0) {
1294 d->decoration_manager = wl_registry_bind(d->registry, id, &zxdg_decoration_manager_v1_interface, 1);
1295 } else if (SDL_strcmp(interface, "zwp_tablet_manager_v2") == 0) {
1296 d->tablet_manager = wl_registry_bind(d->registry, id, &zwp_tablet_manager_v2_interface, 1);
1297 Wayland_input_init_tablet_support(d->input, d->tablet_manager);
1298 } else if (SDL_strcmp(interface, "zxdg_output_manager_v1") == 0) {
1299 version = SDL_min(version, 3); // Versions 1 through 3 are supported.
1300 d->xdg_output_manager = wl_registry_bind(d->registry, id, &zxdg_output_manager_v1_interface, version);
1301 Wayland_init_xdg_output(d);
1302 } else if (SDL_strcmp(interface, "wp_viewporter") == 0) {
1303 d->viewporter = wl_registry_bind(d->registry, id, &wp_viewporter_interface, 1);
1304 } else if (SDL_strcmp(interface, "wp_fractional_scale_manager_v1") == 0) {
1305 d->fractional_scale_manager = wl_registry_bind(d->registry, id, &wp_fractional_scale_manager_v1_interface, 1);
1306 } else if (SDL_strcmp(interface, "zwp_input_timestamps_manager_v1") == 0) {
1307 d->input_timestamps_manager = wl_registry_bind(d->registry, id, &zwp_input_timestamps_manager_v1_interface, 1);
1308 if (d->input) {
1309 Wayland_RegisterTimestampListeners(d->input);
1310 }
1311 } else if (SDL_strcmp(interface, "wp_cursor_shape_manager_v1") == 0) {
1312 d->cursor_shape_manager = wl_registry_bind(d->registry, id, &wp_cursor_shape_manager_v1_interface, 1);
1313 if (d->input) {
1314 Wayland_CreateCursorShapeDevice(d->input);
1315 }
1316 } else if (SDL_strcmp(interface, "zxdg_exporter_v2") == 0) {
1317 d->zxdg_exporter_v2 = wl_registry_bind(d->registry, id, &zxdg_exporter_v2_interface, 1);
1318 } else if (SDL_strcmp(interface, "xdg_wm_dialog_v1") == 0) {
1319 d->xdg_wm_dialog_v1 = wl_registry_bind(d->registry, id, &xdg_wm_dialog_v1_interface, 1);
1320 } else if (SDL_strcmp(interface, "wp_alpha_modifier_v1") == 0) {
1321 d->wp_alpha_modifier_v1 = wl_registry_bind(d->registry, id, &wp_alpha_modifier_v1_interface, 1);
1322 } else if (SDL_strcmp(interface, "xdg_toplevel_icon_manager_v1") == 0) {
1323 d->xdg_toplevel_icon_manager_v1 = wl_registry_bind(d->registry, id, &xdg_toplevel_icon_manager_v1_interface, 1);
1324 } else if (SDL_strcmp(interface, "frog_color_management_factory_v1") == 0) {
1325 d->frog_color_management_factory_v1 = wl_registry_bind(d->registry, id, &frog_color_management_factory_v1_interface, 1);
1326 } else if (SDL_strcmp(interface, "wp_color_manager_v1") == 0) {
1327 d->wp_color_manager_v1 = wl_registry_bind(d->registry, id, &wp_color_manager_v1_interface, 1);
1328 Wayland_InitColorManager(d);
1329 }
1330}
1331
1332static void display_remove_global(void *data, struct wl_registry *registry, uint32_t id)
1333{
1334 SDL_VideoData *d = data;
1335
1336 // We don't get an interface, just an ID, so assume it's a wl_output :shrug:
1337 for (int i = 0; i < d->output_count; ++i) {
1338 SDL_DisplayData *disp = d->output_list[i];
1339 if (disp->registry_id == id) {
1340 Wayland_free_display(SDL_GetVideoDisplay(disp->display), true);
1341
1342 if (i < d->output_count) {
1343 SDL_memmove(&d->output_list[i], &d->output_list[i + 1], sizeof(SDL_DisplayData *) * (d->output_count - i - 1));
1344 }
1345
1346 d->output_count--;
1347 break;
1348 }
1349 }
1350}
1351
1352static const struct wl_registry_listener registry_listener = {
1353 display_handle_global,
1354 display_remove_global
1355};
1356
1357#ifdef HAVE_LIBDECOR_H
1358static bool should_use_libdecor(SDL_VideoData *data, bool ignore_xdg)
1359{
1360 if (!SDL_WAYLAND_HAVE_WAYLAND_LIBDECOR) {
1361 return false;
1362 }
1363
1364 if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_ALLOW_LIBDECOR, true)) {
1365 return false;
1366 }
1367
1368 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_PREFER_LIBDECOR, false)) {
1369 return true;
1370 }
1371
1372 if (ignore_xdg) {
1373 return true;
1374 }
1375
1376 if (data->decoration_manager) {
1377 return false;
1378 }
1379
1380 return true;
1381}
1382#endif
1383
1384bool Wayland_LoadLibdecor(SDL_VideoData *data, bool ignore_xdg)
1385{
1386#ifdef HAVE_LIBDECOR_H
1387 if (data->shell.libdecor != NULL) {
1388 return true; // Already loaded!
1389 }
1390 if (should_use_libdecor(data, ignore_xdg)) {
1391 data->shell.libdecor = libdecor_new(data->display, &libdecor_interface);
1392 return data->shell.libdecor != NULL;
1393 }
1394#endif
1395 return false;
1396}
1397
1398bool Wayland_VideoInit(SDL_VideoDevice *_this)
1399{
1400 SDL_VideoData *data = _this->internal;
1401
1402 data->xkb_context = WAYLAND_xkb_context_new(0);
1403 if (!data->xkb_context) {
1404 return SDL_SetError("Failed to create XKB context");
1405 }
1406
1407 data->registry = wl_display_get_registry(data->display);
1408 if (!data->registry) {
1409 return SDL_SetError("Failed to get the Wayland registry");
1410 }
1411
1412 wl_registry_add_listener(data->registry, &registry_listener, data);
1413
1414 // First roundtrip to receive all registry objects.
1415 WAYLAND_wl_display_roundtrip(data->display);
1416
1417 // Require viewports and xdg-output for display scaling.
1418 if (data->scale_to_display_enabled) {
1419 if (!data->viewporter) {
1420 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Display scaling requires the missing 'wp_viewporter' protocol: disabling");
1421 data->scale_to_display_enabled = false;
1422 }
1423 if (!data->xdg_output_manager) {
1424 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Display scaling requires the missing 'zxdg_output_manager_v1' protocol: disabling");
1425 data->scale_to_display_enabled = false;
1426 }
1427 }
1428
1429 // Now that we have all the protocols, load libdecor if applicable
1430 Wayland_LoadLibdecor(data, false);
1431
1432 // Second roundtrip to receive all output events.
1433 WAYLAND_wl_display_roundtrip(data->display);
1434
1435 Wayland_FinalizeDisplays(data);
1436
1437 Wayland_InitMouse();
1438
1439 WAYLAND_wl_display_flush(data->display);
1440
1441 Wayland_InitKeyboard(_this);
1442
1443 if (data->primary_selection_device_manager) {
1444 _this->SetPrimarySelectionText = Wayland_SetPrimarySelectionText;
1445 _this->GetPrimarySelectionText = Wayland_GetPrimarySelectionText;
1446 _this->HasPrimarySelectionText = Wayland_HasPrimarySelectionText;
1447 }
1448
1449 data->initializing = false;
1450
1451 return true;
1452}
1453
1454static bool Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
1455{
1456 SDL_VideoData *viddata = _this->internal;
1457 SDL_DisplayData *internal = display->internal;
1458 rect->x = internal->x;
1459 rect->y = internal->y;
1460
1461 // When an emulated, exclusive fullscreen window has focus, treat the mode dimensions as the display bounds.
1462 if (display->fullscreen_window &&
1463 display->fullscreen_window->fullscreen_exclusive &&
1464 display->fullscreen_window->internal->active &&
1465 display->fullscreen_window->current_fullscreen_mode.w != 0 &&
1466 display->fullscreen_window->current_fullscreen_mode.h != 0) {
1467 rect->w = display->fullscreen_window->current_fullscreen_mode.w;
1468 rect->h = display->fullscreen_window->current_fullscreen_mode.h;
1469 } else {
1470 if (!viddata->scale_to_display_enabled) {
1471 rect->w = display->current_mode->w;
1472 rect->h = display->current_mode->h;
1473 } else if (internal->transform & WL_OUTPUT_TRANSFORM_90) {
1474 rect->w = internal->pixel_height;
1475 rect->h = internal->pixel_width;
1476 } else {
1477 rect->w = internal->pixel_width;
1478 rect->h = internal->pixel_height;
1479 }
1480 }
1481 return true;
1482}
1483
1484static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
1485{
1486 SDL_VideoData *data = _this->internal;
1487 int i;
1488
1489 Wayland_FiniMouse(data);
1490
1491 for (i = _this->num_displays - 1; i >= 0; --i) {
1492 SDL_VideoDisplay *display = _this->displays[i];
1493 Wayland_free_display(display, false);
1494 }
1495 SDL_free(data->output_list);
1496
1497 Wayland_display_destroy_input(data);
1498
1499 if (data->pointer_constraints) {
1500 zwp_pointer_constraints_v1_destroy(data->pointer_constraints);
1501 data->pointer_constraints = NULL;
1502 }
1503
1504 if (data->relative_pointer_manager) {
1505 zwp_relative_pointer_manager_v1_destroy(data->relative_pointer_manager);
1506 data->relative_pointer_manager = NULL;
1507 }
1508
1509 if (data->activation_manager) {
1510 xdg_activation_v1_destroy(data->activation_manager);
1511 data->activation_manager = NULL;
1512 }
1513
1514 if (data->idle_inhibit_manager) {
1515 zwp_idle_inhibit_manager_v1_destroy(data->idle_inhibit_manager);
1516 data->idle_inhibit_manager = NULL;
1517 }
1518
1519 if (data->key_inhibitor_manager) {
1520 zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(data->key_inhibitor_manager);
1521 data->key_inhibitor_manager = NULL;
1522 }
1523
1524 Wayland_QuitKeyboard(_this);
1525
1526 if (data->text_input_manager) {
1527 zwp_text_input_manager_v3_destroy(data->text_input_manager);
1528 data->text_input_manager = NULL;
1529 }
1530
1531 if (data->xkb_context) {
1532 WAYLAND_xkb_context_unref(data->xkb_context);
1533 data->xkb_context = NULL;
1534 }
1535
1536 if (data->tablet_manager) {
1537 zwp_tablet_manager_v2_destroy((struct zwp_tablet_manager_v2 *)data->tablet_manager);
1538 data->tablet_manager = NULL;
1539 }
1540
1541 if (data->data_device_manager) {
1542 wl_data_device_manager_destroy(data->data_device_manager);
1543 data->data_device_manager = NULL;
1544 }
1545
1546 if (data->shm) {
1547 wl_shm_destroy(data->shm);
1548 data->shm = NULL;
1549 }
1550
1551 if (data->shell.xdg) {
1552 xdg_wm_base_destroy(data->shell.xdg);
1553 data->shell.xdg = NULL;
1554 }
1555
1556 if (data->decoration_manager) {
1557 zxdg_decoration_manager_v1_destroy(data->decoration_manager);
1558 data->decoration_manager = NULL;
1559 }
1560
1561 if (data->xdg_output_manager) {
1562 zxdg_output_manager_v1_destroy(data->xdg_output_manager);
1563 data->xdg_output_manager = NULL;
1564 }
1565
1566 if (data->viewporter) {
1567 wp_viewporter_destroy(data->viewporter);
1568 data->viewporter = NULL;
1569 }
1570
1571 if (data->primary_selection_device_manager) {
1572 zwp_primary_selection_device_manager_v1_destroy(data->primary_selection_device_manager);
1573 data->primary_selection_device_manager = NULL;
1574 }
1575
1576 if (data->fractional_scale_manager) {
1577 wp_fractional_scale_manager_v1_destroy(data->fractional_scale_manager);
1578 data->fractional_scale_manager = NULL;
1579 }
1580
1581 if (data->input_timestamps_manager) {
1582 zwp_input_timestamps_manager_v1_destroy(data->input_timestamps_manager);
1583 data->input_timestamps_manager = NULL;
1584 }
1585
1586 if (data->cursor_shape_manager) {
1587 wp_cursor_shape_manager_v1_destroy(data->cursor_shape_manager);
1588 data->cursor_shape_manager = NULL;
1589 }
1590
1591 if (data->zxdg_exporter_v2) {
1592 zxdg_exporter_v2_destroy(data->zxdg_exporter_v2);
1593 data->zxdg_exporter_v2 = NULL;
1594 }
1595
1596 if (data->xdg_wm_dialog_v1) {
1597 xdg_wm_dialog_v1_destroy(data->xdg_wm_dialog_v1);
1598 data->xdg_wm_dialog_v1 = NULL;
1599 }
1600
1601 if (data->wp_alpha_modifier_v1) {
1602 wp_alpha_modifier_v1_destroy(data->wp_alpha_modifier_v1);
1603 data->wp_alpha_modifier_v1 = NULL;
1604 }
1605
1606 if (data->xdg_toplevel_icon_manager_v1) {
1607 xdg_toplevel_icon_manager_v1_destroy(data->xdg_toplevel_icon_manager_v1);
1608 data->xdg_toplevel_icon_manager_v1 = NULL;
1609 }
1610
1611 if (data->frog_color_management_factory_v1) {
1612 frog_color_management_factory_v1_destroy(data->frog_color_management_factory_v1);
1613 data->frog_color_management_factory_v1 = NULL;
1614 }
1615
1616 if (data->wp_color_manager_v1) {
1617 wp_color_manager_v1_destroy(data->wp_color_manager_v1);
1618 data->wp_color_manager_v1 = NULL;
1619 }
1620
1621 if (data->compositor) {
1622 wl_compositor_destroy(data->compositor);
1623 data->compositor = NULL;
1624 }
1625
1626 if (data->registry) {
1627 wl_registry_destroy(data->registry);
1628 data->registry = NULL;
1629 }
1630}
1631
1632bool Wayland_VideoReconnect(SDL_VideoDevice *_this)
1633{
1634#if 0 // TODO RECONNECT: Uncomment all when https://invent.kde.org/plasma/kwin/-/wikis/Restarting is completed
1635 SDL_VideoData *data = _this->internal;
1636
1637 SDL_Window *window = NULL;
1638
1639 SDL_GLContext current_ctx = SDL_GL_GetCurrentContext();
1640 SDL_Window *current_window = SDL_GL_GetCurrentWindow();
1641
1642 SDL_GL_MakeCurrent(NULL, NULL);
1643 Wayland_VideoCleanup(_this);
1644
1645 SDL_ResetKeyboard();
1646 SDL_ResetMouse();
1647 if (WAYLAND_wl_display_reconnect(data->display) < 0) {
1648 return false;
1649 }
1650
1651 Wayland_VideoInit(_this);
1652
1653 window = _this->windows;
1654 while (window) {
1655 /* We're going to cheat _just_ for a second and strip the OpenGL flag.
1656 * The Wayland driver actually forces it in CreateWindow, and
1657 * RecreateWindow does a bunch of unloading/loading of libGL, so just
1658 * strip the flag so RecreateWindow doesn't mess with the GL context,
1659 * and CreateWindow will add it right back!
1660 * -flibit
1661 */
1662 window->flags &= ~SDL_WINDOW_OPENGL;
1663
1664 SDL_RecreateWindow(window, window->flags);
1665 window = window->next;
1666 }
1667
1668 Wayland_RecreateCursors();
1669
1670 if (current_window && current_ctx) {
1671 SDL_GL_MakeCurrent (current_window, current_ctx);
1672 }
1673 return true;
1674#else
1675 return false;
1676#endif // 0
1677}
1678
1679void Wayland_VideoQuit(SDL_VideoDevice *_this)
1680{
1681 Wayland_VideoCleanup(_this);
1682
1683#ifdef HAVE_LIBDECOR_H
1684 SDL_VideoData *data = _this->internal;
1685 if (data->shell.libdecor) {
1686 libdecor_unref(data->shell.libdecor);
1687 data->shell.libdecor = NULL;
1688 }
1689#endif
1690}
1691
1692#endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvideo.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvideo.h
new file mode 100644
index 0000000..8cde64c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvideo.h
@@ -0,0 +1,140 @@
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#ifndef SDL_waylandvideo_h_
25#define SDL_waylandvideo_h_
26
27#include <EGL/egl.h>
28#include "wayland-util.h"
29
30#include "../SDL_sysvideo.h"
31#include "../../core/linux/SDL_dbus.h"
32#include "../../core/linux/SDL_ime.h"
33
34struct xkb_context;
35struct SDL_WaylandInput;
36
37typedef struct
38{
39 struct wl_cursor_theme *theme;
40 int size;
41} SDL_WaylandCursorTheme;
42
43typedef struct
44{
45 struct wl_list link;
46 char wl_output_name[];
47} SDL_WaylandConnectorName;
48
49struct SDL_VideoData
50{
51 bool initializing;
52 struct wl_display *display;
53 int display_disconnected;
54 struct wl_registry *registry;
55 struct wl_compositor *compositor;
56 struct wl_shm *shm;
57 SDL_WaylandCursorTheme *cursor_themes;
58 int num_cursor_themes;
59 struct wl_pointer *pointer;
60 struct
61 {
62 struct xdg_wm_base *xdg;
63#ifdef HAVE_LIBDECOR_H
64 struct libdecor *libdecor;
65#endif
66 } shell;
67 struct zwp_relative_pointer_manager_v1 *relative_pointer_manager;
68 struct zwp_pointer_constraints_v1 *pointer_constraints;
69 struct wp_cursor_shape_manager_v1 *cursor_shape_manager;
70 struct wl_data_device_manager *data_device_manager;
71 struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager;
72 struct zxdg_decoration_manager_v1 *decoration_manager;
73 struct zwp_keyboard_shortcuts_inhibit_manager_v1 *key_inhibitor_manager;
74 struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager;
75 struct xdg_activation_v1 *activation_manager;
76 struct zwp_text_input_manager_v3 *text_input_manager;
77 struct zxdg_output_manager_v1 *xdg_output_manager;
78 struct wp_viewporter *viewporter;
79 struct wp_fractional_scale_manager_v1 *fractional_scale_manager;
80 struct zwp_input_timestamps_manager_v1 *input_timestamps_manager;
81 struct zxdg_exporter_v2 *zxdg_exporter_v2;
82 struct xdg_wm_dialog_v1 *xdg_wm_dialog_v1;
83 struct wp_alpha_modifier_v1 *wp_alpha_modifier_v1;
84 struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1;
85 struct frog_color_management_factory_v1 *frog_color_management_factory_v1;
86 struct wp_color_manager_v1 *wp_color_manager_v1;
87 struct zwp_tablet_manager_v2 *tablet_manager;
88
89 struct xkb_context *xkb_context;
90 struct SDL_WaylandInput *input;
91 SDL_DisplayData **output_list;
92 int output_count;
93 int output_max;
94
95 int relative_mouse_mode;
96 bool display_externally_owned;
97
98 bool scale_to_display_enabled;
99};
100
101struct SDL_DisplayData
102{
103 SDL_VideoData *videodata;
104 struct wl_output *output;
105 struct zxdg_output_v1 *xdg_output;
106 struct wp_color_management_output_v1 *wp_color_management_output;
107 char *wl_output_name;
108 double scale_factor;
109 uint32_t registry_id;
110 int logical_width, logical_height;
111 int pixel_width, pixel_height;
112 int x, y, refresh, transform;
113 SDL_DisplayOrientation orientation;
114 int physical_width_mm, physical_height_mm;
115 bool has_logical_position, has_logical_size;
116 bool running_colorspace_event_queue;
117 SDL_HDROutputProperties HDR;
118 SDL_DisplayID display;
119 SDL_VideoDisplay placeholder;
120 int wl_output_done_count;
121 struct Wayland_ColorInfoState *color_info_state;
122};
123
124// Needed here to get wl_surface declaration, fixes GitHub#4594
125#include "SDL_waylanddyn.h"
126
127extern void SDL_WAYLAND_register_surface(struct wl_surface *surface);
128extern void SDL_WAYLAND_register_output(struct wl_output *output);
129extern bool SDL_WAYLAND_own_surface(struct wl_surface *surface);
130extern bool SDL_WAYLAND_own_output(struct wl_output *output);
131
132extern SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface);
133void Wayland_AddWindowDataToExternalList(SDL_WindowData *data);
134void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data);
135
136extern bool Wayland_LoadLibdecor(SDL_VideoData *data, bool ignore_xdg);
137
138extern bool Wayland_VideoReconnect(SDL_VideoDevice *_this);
139
140#endif // SDL_waylandvideo_h_
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvulkan.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvulkan.c
new file mode 100644
index 0000000..956be46
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvulkan.c
@@ -0,0 +1,203 @@
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/*
23 * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's
24 * SDL_x11vulkan.c.
25 */
26
27#include "SDL_internal.h"
28
29#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_WAYLAND)
30
31#include "../SDL_vulkan_internal.h"
32
33#include "SDL_waylandvideo.h"
34#include "SDL_waylandwindow.h"
35
36#include "SDL_waylandvulkan.h"
37
38#ifdef SDL_PLATFORM_OPENBSD
39#define DEFAULT_VULKAN "libvulkan.so"
40#else
41#define DEFAULT_VULKAN "libvulkan.so.1"
42#endif
43
44bool Wayland_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path)
45{
46 VkExtensionProperties *extensions = NULL;
47 Uint32 i, extensionCount = 0;
48 bool hasSurfaceExtension = false;
49 bool hasWaylandSurfaceExtension = false;
50 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
51 if (_this->vulkan_config.loader_handle) {
52 return SDL_SetError("Vulkan already loaded");
53 }
54
55 // Load the Vulkan loader library
56 if (!path) {
57 path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY);
58 }
59 if (!path) {
60 path = DEFAULT_VULKAN;
61 }
62 _this->vulkan_config.loader_handle = SDL_LoadObject(path);
63 if (!_this->vulkan_config.loader_handle) {
64 return false;
65 }
66 SDL_strlcpy(_this->vulkan_config.loader_path, path,
67 SDL_arraysize(_this->vulkan_config.loader_path));
68 vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction(
69 _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr");
70 if (!vkGetInstanceProcAddr) {
71 goto fail;
72 }
73 _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr;
74 _this->vulkan_config.vkEnumerateInstanceExtensionProperties =
75 (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)(
76 VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties");
77 if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) {
78 goto fail;
79 }
80 extensions = SDL_Vulkan_CreateInstanceExtensionsList(
81 (PFN_vkEnumerateInstanceExtensionProperties)
82 _this->vulkan_config.vkEnumerateInstanceExtensionProperties,
83 &extensionCount);
84 if (!extensions) {
85 goto fail;
86 }
87 for (i = 0; i < extensionCount; i++) {
88 if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
89 hasSurfaceExtension = true;
90 } else if (SDL_strcmp(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
91 hasWaylandSurfaceExtension = true;
92 }
93 }
94 SDL_free(extensions);
95 if (!hasSurfaceExtension) {
96 SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension");
97 goto fail;
98 } else if (!hasWaylandSurfaceExtension) {
99 SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME "extension");
100 goto fail;
101 }
102 return true;
103
104fail:
105 SDL_UnloadObject(_this->vulkan_config.loader_handle);
106 _this->vulkan_config.loader_handle = NULL;
107 return false;
108}
109
110void Wayland_Vulkan_UnloadLibrary(SDL_VideoDevice *_this)
111{
112 if (_this->vulkan_config.loader_handle) {
113 SDL_UnloadObject(_this->vulkan_config.loader_handle);
114 _this->vulkan_config.loader_handle = NULL;
115 }
116}
117
118char const* const* Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count)
119{
120 static const char *const extensionsForWayland[] = {
121 VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME
122 };
123
124 if (count) {
125 *count = SDL_arraysize(extensionsForWayland);
126 }
127
128 return extensionsForWayland;
129}
130
131bool Wayland_Vulkan_CreateSurface(SDL_VideoDevice *_this,
132 SDL_Window *window,
133 VkInstance instance,
134 const struct VkAllocationCallbacks *allocator,
135 VkSurfaceKHR *surface)
136{
137 SDL_WindowData *windowData = window->internal;
138 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
139 (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
140 PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR =
141 (PFN_vkCreateWaylandSurfaceKHR)vkGetInstanceProcAddr(
142 instance,
143 "vkCreateWaylandSurfaceKHR");
144 VkWaylandSurfaceCreateInfoKHR createInfo;
145 VkResult result;
146
147 if (!_this->vulkan_config.loader_handle) {
148 return SDL_SetError("Vulkan is not loaded");
149 }
150
151 if (!vkCreateWaylandSurfaceKHR) {
152 return SDL_SetError(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME
153 " extension is not enabled in the Vulkan instance.");
154 }
155 SDL_zero(createInfo);
156 createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
157 createInfo.pNext = NULL;
158 createInfo.flags = 0;
159 createInfo.display = windowData->waylandData->display;
160 createInfo.surface = windowData->surface;
161 result = vkCreateWaylandSurfaceKHR(instance, &createInfo, allocator, surface);
162 if (result != VK_SUCCESS) {
163 return SDL_SetError("vkCreateWaylandSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result));
164 }
165 return true;
166}
167
168void Wayland_Vulkan_DestroySurface(SDL_VideoDevice *_this,
169 VkInstance instance,
170 VkSurfaceKHR surface,
171 const struct VkAllocationCallbacks *allocator)
172{
173 if (_this->vulkan_config.loader_handle) {
174 SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
175 }
176}
177
178bool Wayland_Vulkan_GetPresentationSupport(SDL_VideoDevice *_this,
179 VkInstance instance,
180 VkPhysicalDevice physicalDevice,
181 Uint32 queueFamilyIndex)
182{
183 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
184 (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
185 PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR =
186 (PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR)vkGetInstanceProcAddr(
187 instance,
188 "vkGetPhysicalDeviceWaylandPresentationSupportKHR");
189
190 if (!_this->vulkan_config.loader_handle) {
191 return SDL_SetError("Vulkan is not loaded");
192 }
193
194 if (!vkGetPhysicalDeviceWaylandPresentationSupportKHR) {
195 return SDL_SetError(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance.");
196 }
197
198 return vkGetPhysicalDeviceWaylandPresentationSupportKHR(physicalDevice,
199 queueFamilyIndex,
200 _this->internal->display);
201}
202
203#endif
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvulkan.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvulkan.h
new file mode 100644
index 0000000..58be0bc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandvulkan.h
@@ -0,0 +1,55 @@
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/*
23 * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's
24 * SDL_x11vulkan.h.
25 */
26
27#include "SDL_internal.h"
28
29#ifndef SDL_waylandvulkan_h_
30#define SDL_waylandvulkan_h_
31
32#include <SDL3/SDL_vulkan.h>
33
34#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_WAYLAND)
35
36extern bool Wayland_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
37extern void Wayland_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
38extern char const* const* Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count);
39extern bool Wayland_Vulkan_CreateSurface(SDL_VideoDevice *_this,
40 SDL_Window *window,
41 VkInstance instance,
42 const struct VkAllocationCallbacks *allocator,
43 VkSurfaceKHR *surface);
44extern void Wayland_Vulkan_DestroySurface(SDL_VideoDevice *_this,
45 VkInstance instance,
46 VkSurfaceKHR surface,
47 const struct VkAllocationCallbacks *allocator);
48extern bool Wayland_Vulkan_GetPresentationSupport(SDL_VideoDevice *_this,
49 VkInstance instance,
50 VkPhysicalDevice physicalDevice,
51 Uint32 queueFamilyIndex);
52
53#endif
54
55#endif // SDL_waylandvulkan_h_
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.c
new file mode 100644
index 0000000..212fd54
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.c
@@ -0,0 +1,3125 @@
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 <sys/mman.h>
27
28#include "../SDL_sysvideo.h"
29#include "../../events/SDL_events_c.h"
30#include "../../core/unix/SDL_appid.h"
31#include "../SDL_egl_c.h"
32#include "SDL_waylandevents_c.h"
33#include "SDL_waylandwindow.h"
34#include "SDL_waylandvideo.h"
35#include "../../SDL_hints_c.h"
36#include "SDL_waylandcolor.h"
37
38#include "alpha-modifier-v1-client-protocol.h"
39#include "xdg-shell-client-protocol.h"
40#include "xdg-decoration-unstable-v1-client-protocol.h"
41#include "idle-inhibit-unstable-v1-client-protocol.h"
42#include "xdg-activation-v1-client-protocol.h"
43#include "viewporter-client-protocol.h"
44#include "fractional-scale-v1-client-protocol.h"
45#include "xdg-foreign-unstable-v2-client-protocol.h"
46#include "xdg-dialog-v1-client-protocol.h"
47#include "frog-color-management-v1-client-protocol.h"
48#include "xdg-toplevel-icon-v1-client-protocol.h"
49#include "color-management-v1-client-protocol.h"
50
51#ifdef HAVE_LIBDECOR_H
52#include <libdecor.h>
53#endif
54
55static double GetWindowScale(SDL_Window *window)
56{
57 return (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) || window->internal->scale_to_display ? window->internal->scale_factor : 1.0;
58}
59
60// These are point->pixel->point round trip safe; the inverse is not round trip safe due to rounding.
61static int PointToPixel(SDL_Window *window, int point)
62{
63 /* Rounds halfway away from zero as per the Wayland fractional scaling protocol spec.
64 * Wayland scale units are in units of 1/120, so the offset is required to correct for
65 * rounding errors when using certain scale values.
66 */
67 return point ? SDL_max((int)SDL_lround((double)point * GetWindowScale(window) + 1e-6), 1) : 0;
68}
69
70static int PixelToPoint(SDL_Window *window, int pixel)
71{
72 return pixel ? SDL_max((int)SDL_lround((double)pixel / GetWindowScale(window)), 1) : 0;
73}
74
75/* According to the Wayland spec:
76 *
77 * "If the [fullscreen] surface doesn't cover the whole output, the compositor will
78 * position the surface in the center of the output and compensate with border fill
79 * covering the rest of the output. The content of the border fill is undefined, but
80 * should be assumed to be in some way that attempts to blend into the surrounding area
81 * (e.g. solid black)."
82 *
83 * - KDE, as of 5.27, still doesn't do this
84 * - GNOME prior to 43 didn't do this (older versions are still found in many LTS distros)
85 *
86 * Default to 'stretch' for now, until things have moved forward enough that the default
87 * can be changed to 'aspect'.
88 */
89enum WaylandModeScale
90{
91 WAYLAND_MODE_SCALE_UNDEFINED,
92 WAYLAND_MODE_SCALE_ASPECT,
93 WAYLAND_MODE_SCALE_STRETCH,
94 WAYLAND_MODE_SCALE_NONE
95};
96
97static enum WaylandModeScale GetModeScaleMethod(void)
98{
99 static enum WaylandModeScale scale_mode = WAYLAND_MODE_SCALE_UNDEFINED;
100
101 if (scale_mode == WAYLAND_MODE_SCALE_UNDEFINED) {
102 const char *scale_hint = SDL_GetHint(SDL_HINT_VIDEO_WAYLAND_MODE_SCALING);
103
104 if (scale_hint) {
105 if (!SDL_strcasecmp(scale_hint, "aspect")) {
106 scale_mode = WAYLAND_MODE_SCALE_ASPECT;
107 } else if (!SDL_strcasecmp(scale_hint, "none")) {
108 scale_mode = WAYLAND_MODE_SCALE_NONE;
109 } else {
110 scale_mode = WAYLAND_MODE_SCALE_STRETCH;
111 }
112 } else {
113 scale_mode = WAYLAND_MODE_SCALE_STRETCH;
114 }
115 }
116
117 return scale_mode;
118}
119
120static void GetBufferSize(SDL_Window *window, int *width, int *height)
121{
122 SDL_WindowData *data = window->internal;
123 int buf_width;
124 int buf_height;
125
126 // Exclusive fullscreen modes always have a pixel density of 1
127 if (data->is_fullscreen && window->fullscreen_exclusive) {
128 buf_width = window->current_fullscreen_mode.w;
129 buf_height = window->current_fullscreen_mode.h;
130 } else if (!data->scale_to_display) {
131 // Round fractional backbuffer sizes halfway away from zero.
132 buf_width = PointToPixel(window, data->requested.logical_width);
133 buf_height = PointToPixel(window, data->requested.logical_height);
134 } else {
135 buf_width = data->requested.pixel_width;
136 buf_height = data->requested.pixel_height;
137 }
138
139 if (width) {
140 *width = buf_width;
141 }
142 if (height) {
143 *height = buf_height;
144 }
145}
146
147static void SetMinMaxDimensions(SDL_Window *window)
148{
149 SDL_WindowData *wind = window->internal;
150 int min_width, min_height, max_width, max_height;
151
152 if ((window->flags & SDL_WINDOW_FULLSCREEN) || wind->fullscreen_deadline_count) {
153 min_width = 0;
154 min_height = 0;
155 max_width = 0;
156 max_height = 0;
157 } else if (window->flags & SDL_WINDOW_RESIZABLE) {
158 int adj_w = SDL_max(window->min_w, wind->system_limits.min_width);
159 int adj_h = SDL_max(window->min_h, wind->system_limits.min_height);
160 if (wind->scale_to_display) {
161 adj_w = PixelToPoint(window, adj_w);
162 adj_h = PixelToPoint(window, adj_h);
163 }
164 min_width = adj_w;
165 min_height = adj_h;
166
167 adj_w = window->max_w ? SDL_max(window->max_w, wind->system_limits.min_width) : 0;
168 adj_h = window->max_h ? SDL_max(window->max_h, wind->system_limits.min_height) : 0;
169 if (wind->scale_to_display) {
170 adj_w = PixelToPoint(window, adj_w);
171 adj_h = PixelToPoint(window, adj_h);
172 }
173 max_width = adj_w;
174 max_height = adj_h;
175 } else {
176 min_width = wind->current.logical_width;
177 min_height = wind->current.logical_height;
178 max_width = wind->current.logical_width;
179 max_height = wind->current.logical_height;
180 }
181
182#ifdef HAVE_LIBDECOR_H
183 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
184 if (!wind->shell_surface.libdecor.initial_configure_seen || !wind->shell_surface.libdecor.frame) {
185 return; // Can't do anything yet, wait for ShowWindow
186 }
187 /* No need to change these values if the window is non-resizable,
188 * as libdecor will just overwrite them internally.
189 */
190 if (libdecor_frame_has_capability(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE)) {
191 libdecor_frame_set_min_content_size(wind->shell_surface.libdecor.frame,
192 min_width,
193 min_height);
194 libdecor_frame_set_max_content_size(wind->shell_surface.libdecor.frame,
195 max_width,
196 max_height);
197 }
198 } else
199#endif
200 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
201 if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) {
202 return; // Can't do anything yet, wait for ShowWindow
203 }
204 xdg_toplevel_set_min_size(wind->shell_surface.xdg.toplevel.xdg_toplevel,
205 min_width,
206 min_height);
207 xdg_toplevel_set_max_size(wind->shell_surface.xdg.toplevel.xdg_toplevel,
208 max_width,
209 max_height);
210 }
211}
212
213static void EnsurePopupPositionIsValid(SDL_Window *window, int *x, int *y)
214{
215 int adj_count = 0;
216
217 /* Per the xdg-positioner spec, child popup windows must intersect or at
218 * least be partially adjacent to the parent window.
219 *
220 * Failure to ensure this on a compositor that enforces this restriction
221 * can result in behavior ranging from the window being spuriously closed
222 * to a protocol violation.
223 */
224 if (*x + window->w < 0) {
225 *x = -window->w;
226 ++adj_count;
227 }
228 if (*y + window->h < 0) {
229 *y = -window->h;
230 ++adj_count;
231 }
232 if (*x > window->parent->w) {
233 *x = window->parent->w;
234 ++adj_count;
235 }
236 if (*y > window->parent->h) {
237 *y = window->parent->h;
238 ++adj_count;
239 }
240
241 /* If adjustment was required on the x and y axes, the popup is aligned with
242 * the parent corner-to-corner and is neither overlapping nor adjacent, so it
243 * must be nudged by 1 to be considered adjacent.
244 */
245 if (adj_count > 1) {
246 *x += *x < 0 ? 1 : -1;
247 }
248}
249
250static void AdjustPopupOffset(SDL_Window *popup, int *x, int *y)
251{
252 // Adjust the popup positioning, if necessary
253#ifdef HAVE_LIBDECOR_H
254 if (popup->parent->internal->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
255 int adj_x, adj_y;
256 libdecor_frame_translate_coordinate(popup->parent->internal->shell_surface.libdecor.frame,
257 *x, *y, &adj_x, &adj_y);
258 *x = adj_x;
259 *y = adj_y;
260 }
261#endif
262}
263
264static void RepositionPopup(SDL_Window *window, bool use_current_position)
265{
266 SDL_WindowData *wind = window->internal;
267
268 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP &&
269 wind->shell_surface.xdg.popup.xdg_positioner &&
270 xdg_popup_get_version(wind->shell_surface.xdg.popup.xdg_popup) >= XDG_POPUP_REPOSITION_SINCE_VERSION) {
271 int x = use_current_position ? window->x : window->pending.x;
272 int y = use_current_position ? window->y : window->pending.y;
273
274 EnsurePopupPositionIsValid(window, &x, &y);
275 if (wind->scale_to_display) {
276 x = PixelToPoint(window->parent, x);
277 y = PixelToPoint(window->parent, y);
278 }
279 AdjustPopupOffset(window, &x, &y);
280 xdg_positioner_set_anchor_rect(wind->shell_surface.xdg.popup.xdg_positioner, 0, 0, window->parent->internal->current.logical_width, window->parent->internal->current.logical_height);
281 xdg_positioner_set_size(wind->shell_surface.xdg.popup.xdg_positioner, wind->current.logical_width, wind->current.logical_height);
282 xdg_positioner_set_offset(wind->shell_surface.xdg.popup.xdg_positioner, x, y);
283 xdg_popup_reposition(wind->shell_surface.xdg.popup.xdg_popup,
284 wind->shell_surface.xdg.popup.xdg_positioner,
285 0);
286 }
287}
288
289static void SetSurfaceOpaqueRegion(SDL_WindowData *wind, bool is_opaque)
290{
291 SDL_VideoData *viddata = wind->waylandData;
292
293 if (is_opaque) {
294 struct wl_region *region = wl_compositor_create_region(viddata->compositor);
295 wl_region_add(region, 0, 0,
296 wind->current.logical_width, wind->current.logical_height);
297 wl_surface_set_opaque_region(wind->surface, region);
298 wl_region_destroy(region);
299 } else {
300 wl_surface_set_opaque_region(wind->surface, NULL);
301 }
302}
303
304static bool ConfigureWindowGeometry(SDL_Window *window)
305{
306 SDL_WindowData *data = window->internal;
307 const double scale_factor = GetWindowScale(window);
308 const int old_pixel_width = data->current.pixel_width;
309 const int old_pixel_height = data->current.pixel_height;
310 int window_width, window_height;
311 bool window_size_changed;
312
313 // Throttle interactive resize events to once per refresh cycle to prevent lag.
314 if (data->resizing) {
315 data->resizing = false;
316
317 if (data->drop_interactive_resizes) {
318 return false;
319 } else {
320 data->drop_interactive_resizes = true;
321 }
322 }
323
324 // Set the drawable backbuffer size.
325 GetBufferSize(window, &data->current.pixel_width, &data->current.pixel_height);
326 const bool buffer_size_changed = data->current.pixel_width != old_pixel_width ||
327 data->current.pixel_height != old_pixel_height;
328
329 if (data->egl_window && buffer_size_changed) {
330 WAYLAND_wl_egl_window_resize(data->egl_window,
331 data->current.pixel_width,
332 data->current.pixel_height,
333 0, 0);
334 }
335
336 if (data->is_fullscreen && window->fullscreen_exclusive) {
337 int output_width;
338 int output_height;
339 window_width = window->current_fullscreen_mode.w;
340 window_height = window->current_fullscreen_mode.h;
341
342 output_width = data->requested.logical_width;
343 output_height = data->requested.logical_height;
344
345 switch (GetModeScaleMethod()) {
346 case WAYLAND_MODE_SCALE_NONE:
347 /* The Wayland spec states that the advertised fullscreen dimensions are a maximum.
348 * Windows can request a smaller size, but exceeding these dimensions is a protocol violation,
349 * thus, modes that exceed the output size still need to be scaled with a viewport.
350 */
351 if (window_width <= output_width && window_height <= output_height) {
352 output_width = window_width;
353 output_height = window_height;
354
355 break;
356 }
357 SDL_FALLTHROUGH;
358 case WAYLAND_MODE_SCALE_ASPECT:
359 {
360 const float output_ratio = (float)output_width / (float)output_height;
361 const float mode_ratio = (float)window_width / (float)window_height;
362
363 if (output_ratio > mode_ratio) {
364 output_width = SDL_lroundf((float)window_width * ((float)output_height / (float)window_height));
365 } else if (output_ratio < mode_ratio) {
366 output_height = SDL_lroundf((float)window_height * ((float)output_width / (float)window_width));
367 }
368 } break;
369 default:
370 break;
371 }
372
373 window_size_changed = window_width != window->w || window_height != window->h ||
374 data->current.logical_width != output_width || data->current.logical_height != output_height;
375
376 if (window_size_changed || buffer_size_changed) {
377 if (data->viewport) {
378 wp_viewport_set_destination(data->viewport, output_width, output_height);
379
380 data->current.logical_width = output_width;
381 data->current.logical_height = output_height;
382 } else {
383 // Calculate the integer scale from the mode and output.
384 const int32_t int_scale = SDL_max(window->current_fullscreen_mode.w / output_width, 1);
385
386 wl_surface_set_buffer_scale(data->surface, int_scale);
387 data->current.logical_width = window->current_fullscreen_mode.w;
388 data->current.logical_height = window->current_fullscreen_mode.h;
389 }
390
391 data->pointer_scale.x = (double)window_width / (double)data->current.logical_width;
392 data->pointer_scale.y = (double)window_height / (double)data->current.logical_height;
393 }
394 } else {
395 window_width = data->requested.logical_width;
396 window_height = data->requested.logical_height;
397
398 window_size_changed = window_width != data->current.logical_width || window_height != data->current.logical_height;
399
400 if (window_size_changed || buffer_size_changed) {
401 if (data->viewport) {
402 wp_viewport_set_destination(data->viewport, window_width, window_height);
403 } else if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
404 // Don't change this if the DPI awareness flag is unset, as an application may have set this manually on a custom or external surface.
405 wl_surface_set_buffer_scale(data->surface, (int32_t)scale_factor);
406 }
407
408 // Clamp the physical window size to the system minimum required size.
409 data->current.logical_width = SDL_max(window_width, data->system_limits.min_width);
410 data->current.logical_height = SDL_max(window_height, data->system_limits.min_height);
411
412 if (!data->scale_to_display) {
413 data->pointer_scale.x = 1.0;
414 data->pointer_scale.y = 1.0;
415 } else {
416 data->pointer_scale.x = scale_factor;
417 data->pointer_scale.y = scale_factor;
418 }
419 }
420 }
421
422 /*
423 * The surface geometry, opaque region and pointer confinement region only
424 * need to be recalculated if the output size has changed.
425 */
426 if (window_size_changed) {
427 /* XXX: This is a hack and only set on the xdg-toplevel path when viewports
428 * aren't supported to avoid a potential protocol violation if a buffer
429 * with an old size is committed.
430 */
431 if (!data->viewport && data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && data->shell_surface.xdg.surface) {
432 xdg_surface_set_window_geometry(data->shell_surface.xdg.surface, 0, 0, data->current.logical_width, data->current.logical_height);
433 }
434
435 SetSurfaceOpaqueRegion(data, !(window->flags & SDL_WINDOW_TRANSPARENT) && window->opacity == 1.0f);
436
437 // Ensure that child popup windows are still in bounds.
438 for (SDL_Window *child = window->first_child; child; child = child->next_sibling) {
439 RepositionPopup(child, true);
440 }
441 }
442
443 /* Update the min/max dimensions, primarily if the state was changed, and for non-resizable
444 * xdg-toplevel windows where the limits should match the window size.
445 */
446 SetMinMaxDimensions(window);
447
448 // Unconditionally send the window and drawable size, the video core will deduplicate when required.
449 if (!data->scale_to_display) {
450 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window_width, window_height);
451 } else {
452 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, data->current.pixel_width, data->current.pixel_height);
453 }
454 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, data->current.pixel_width, data->current.pixel_height);
455
456 /* Send an exposure event if the window is in the shown state and the size has changed,
457 * even if the window is occluded, as the client needs to commit a new frame for the
458 * changes to take effect.
459 *
460 * The occlusion state is immediately set again afterward, if necessary.
461 */
462 if (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOWN) {
463 if ((buffer_size_changed || window_size_changed) ||
464 (!data->suspended && (window->flags & SDL_WINDOW_OCCLUDED))) {
465 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
466 }
467
468 if (data->suspended) {
469 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_OCCLUDED, 0, 0);
470 }
471 }
472
473 return true;
474}
475
476static void CommitLibdecorFrame(SDL_Window *window)
477{
478#ifdef HAVE_LIBDECOR_H
479 SDL_WindowData *wind = window->internal;
480
481 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) {
482 struct libdecor_state *state = libdecor_state_new(wind->current.logical_width, wind->current.logical_height);
483 libdecor_frame_commit(wind->shell_surface.libdecor.frame, state, NULL);
484 libdecor_state_free(state);
485 }
486#endif
487}
488
489static void fullscreen_deadline_handler(void *data, struct wl_callback *callback, uint32_t callback_data)
490{
491 // Get the window from the ID as it may have been destroyed
492 SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data);
493 SDL_Window *window = SDL_GetWindowFromID(windowID);
494
495 if (window && window->internal) {
496 window->internal->fullscreen_deadline_count--;
497 }
498
499 wl_callback_destroy(callback);
500}
501
502static struct wl_callback_listener fullscreen_deadline_listener = {
503 fullscreen_deadline_handler
504};
505
506static void maximized_restored_deadline_handler(void *data, struct wl_callback *callback, uint32_t callback_data)
507{
508 // Get the window from the ID as it may have been destroyed
509 SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data);
510 SDL_Window *window = SDL_GetWindowFromID(windowID);
511
512 if (window && window->internal) {
513 window->internal->maximized_restored_deadline_count--;
514 }
515
516 wl_callback_destroy(callback);
517}
518
519static struct wl_callback_listener maximized_restored_deadline_listener = {
520 maximized_restored_deadline_handler
521};
522
523static void FlushPendingEvents(SDL_Window *window)
524{
525 // Serialize and restore the pending flags, as they may be overwritten while flushing.
526 const bool last_position_pending = window->last_position_pending;
527 const bool last_size_pending = window->last_size_pending;
528
529 while (window->internal->fullscreen_deadline_count || window->internal->maximized_restored_deadline_count) {
530 WAYLAND_wl_display_roundtrip(window->internal->waylandData->display);
531 }
532
533 window->last_position_pending = last_position_pending;
534 window->last_size_pending = last_size_pending;
535}
536
537/* While we can't get window position from the compositor, we do at least know
538 * what monitor we're on, so let's send move events that put the window at the
539 * center of the whatever display the wl_surface_listener events give us.
540 */
541static void Wayland_move_window(SDL_Window *window)
542{
543 SDL_WindowData *wind = window->internal;
544 SDL_DisplayData *display;
545 SDL_DisplayID *displays;
546
547 if (wind->outputs && wind->num_outputs) {
548 display = wind->outputs[wind->num_outputs - 1];
549 } else {
550 // A window may not be on any displays if minimized.
551 return;
552 }
553
554 displays = SDL_GetDisplays(NULL);
555 if (displays) {
556 for (int i = 0; displays[i]; ++i) {
557 if (SDL_GetDisplayDriverData(displays[i]) == display) {
558 /* We want to send a very very specific combination here:
559 *
560 * 1. A coordinate that tells the application what display we're on
561 * 2. Exactly (0, 0)
562 *
563 * Part 1 is useful information but is also really important for
564 * ensuring we end up on the right display for fullscreen, while
565 * part 2 is important because numerous applications use a specific
566 * combination of GetWindowPosition and GetGlobalMouseState, and of
567 * course neither are supported by Wayland. Since global mouse will
568 * fall back to just GetMouseState, we need the window position to
569 * be zero so the cursor math works without it going off in some
570 * random direction. See UE5 Editor for a notable example of this!
571 *
572 * This may be an issue some day if we're ever able to implement
573 * SDL_GetDisplayUsableBounds!
574 *
575 * -flibit
576 */
577
578 if (wind->last_displayID != displays[i]) {
579 wind->last_displayID = displays[i];
580 if (wind->shell_surface_type != WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) {
581 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, display->x, display->y);
582 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DISPLAY_CHANGED, wind->last_displayID, 0);
583 }
584 }
585 break;
586 }
587 }
588 SDL_free(displays);
589 }
590}
591
592static void SetFullscreen(SDL_Window *window, struct wl_output *output)
593{
594 SDL_WindowData *wind = window->internal;
595 SDL_VideoData *viddata = wind->waylandData;
596
597#ifdef HAVE_LIBDECOR_H
598 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
599 if (!wind->shell_surface.libdecor.frame) {
600 return; // Can't do anything yet, wait for ShowWindow
601 }
602
603 wind->fullscreen_exclusive = output ? window->fullscreen_exclusive : false;
604 ++wind->fullscreen_deadline_count;
605 if (output) {
606 Wayland_SetWindowResizable(SDL_GetVideoDevice(), window, true);
607 wl_surface_commit(wind->surface);
608
609 libdecor_frame_set_fullscreen(wind->shell_surface.libdecor.frame, output);
610 } else {
611 libdecor_frame_unset_fullscreen(wind->shell_surface.libdecor.frame);
612 }
613 } else
614#endif
615 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
616 if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) {
617 return; // Can't do anything yet, wait for ShowWindow
618 }
619
620 wind->fullscreen_exclusive = output ? window->fullscreen_exclusive : false;
621 ++wind->fullscreen_deadline_count;
622 if (output) {
623 Wayland_SetWindowResizable(SDL_GetVideoDevice(), window, true);
624 wl_surface_commit(wind->surface);
625
626 xdg_toplevel_set_fullscreen(wind->shell_surface.xdg.toplevel.xdg_toplevel, output);
627 } else {
628 xdg_toplevel_unset_fullscreen(wind->shell_surface.xdg.toplevel.xdg_toplevel);
629 }
630 }
631
632 // Queue a deadline event
633 struct wl_callback *cb = wl_display_sync(viddata->display);
634 wl_callback_add_listener(cb, &fullscreen_deadline_listener, (void *)((uintptr_t)window->id));
635}
636
637static void UpdateWindowFullscreen(SDL_Window *window, bool fullscreen)
638{
639 SDL_WindowData *wind = window->internal;
640
641 wind->is_fullscreen = fullscreen;
642
643 if (fullscreen) {
644 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
645 SDL_copyp(&window->current_fullscreen_mode, &window->requested_fullscreen_mode);
646 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0);
647 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_ENTER, false);
648
649 /* Set the output for exclusive fullscreen windows when entering fullscreen from a
650 * compositor event, or if the fullscreen parameters were changed between the initial
651 * fullscreen request and now, to ensure that the window is on the correct output,
652 * as requested by the client.
653 */
654 if (window->fullscreen_exclusive && (!wind->fullscreen_exclusive || !wind->fullscreen_was_positioned)) {
655 SDL_VideoDisplay *disp = SDL_GetVideoDisplay(window->current_fullscreen_mode.displayID);
656 if (disp) {
657 wind->fullscreen_was_positioned = true;
658 SetFullscreen(window, disp->internal->output);
659 }
660 }
661 }
662 } else {
663 // Don't change the fullscreen flags if the window is hidden or being hidden.
664 if ((window->flags & SDL_WINDOW_FULLSCREEN) && !window->is_hiding && !(window->flags & SDL_WINDOW_HIDDEN)) {
665 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0);
666 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_LEAVE, false);
667 wind->fullscreen_was_positioned = false;
668
669 /* Send a move event, in case it was deferred while the fullscreen window was moving and
670 * on multiple outputs.
671 */
672 Wayland_move_window(window);
673 }
674 }
675}
676
677static const struct wl_callback_listener surface_frame_listener;
678
679static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time)
680{
681 SDL_WindowData *wind = (SDL_WindowData *)data;
682
683 /* XXX: This is needed to work around an Nvidia egl-wayland bug due to buffer coordinates
684 * being used with wl_surface_damage, which causes part of the output to not be
685 * updated when using a viewport with an output region larger than the source region.
686 */
687 if (wl_compositor_get_version(wind->waylandData->compositor) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
688 wl_surface_damage_buffer(wind->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
689 } else {
690 wl_surface_damage(wind->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
691 }
692
693 wind->drop_interactive_resizes = false;
694
695 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME) {
696 wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_SHOWN;
697
698 // If any child windows are waiting on this window to be shown, show them now
699 for (SDL_Window *w = wind->sdlwindow->first_child; w; w = w->next_sibling) {
700 if (w->internal->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOW_PENDING) {
701 Wayland_ShowWindow(SDL_GetVideoDevice(), w);
702 } else if (w->internal->reparenting_required) {
703 Wayland_SetWindowParent(SDL_GetVideoDevice(), w, w->parent);
704 if (w->flags & SDL_WINDOW_MODAL) {
705 Wayland_SetWindowModal(SDL_GetVideoDevice(), w, true);
706 }
707 }
708 }
709
710 /* If the window was initially set to the suspended state, send the occluded event now,
711 * as we don't want to mark the window as occluded until at least one frame has been submitted.
712 */
713 if (wind->suspended) {
714 SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_OCCLUDED, 0, 0);
715 }
716 }
717
718 wl_callback_destroy(cb);
719 wind->surface_frame_callback = wl_surface_frame(wind->surface);
720 wl_callback_add_listener(wind->surface_frame_callback, &surface_frame_listener, data);
721}
722
723static const struct wl_callback_listener surface_frame_listener = {
724 surface_frame_done
725};
726
727static const struct wl_callback_listener gles_swap_frame_listener;
728
729static void gles_swap_frame_done(void *data, struct wl_callback *cb, uint32_t time)
730{
731 SDL_WindowData *wind = (SDL_WindowData *)data;
732 SDL_SetAtomicInt(&wind->swap_interval_ready, 1); // mark window as ready to present again.
733
734 // reset this callback to fire again once a new frame was presented and compositor wants the next one.
735 wind->gles_swap_frame_callback = wl_surface_frame(wind->gles_swap_frame_surface_wrapper);
736 wl_callback_destroy(cb);
737 wl_callback_add_listener(wind->gles_swap_frame_callback, &gles_swap_frame_listener, data);
738}
739
740static const struct wl_callback_listener gles_swap_frame_listener = {
741 gles_swap_frame_done
742};
743
744static void handle_configure_xdg_shell_surface(void *data, struct xdg_surface *xdg, uint32_t serial)
745{
746 SDL_WindowData *wind = (SDL_WindowData *)data;
747 SDL_Window *window = wind->sdlwindow;
748
749 if (ConfigureWindowGeometry(window)) {
750 xdg_surface_ack_configure(xdg, serial);
751 }
752
753 wind->shell_surface.xdg.initial_configure_seen = true;
754}
755
756static const struct xdg_surface_listener shell_surface_listener_xdg = {
757 handle_configure_xdg_shell_surface
758};
759
760static void handle_configure_xdg_toplevel(void *data,
761 struct xdg_toplevel *xdg_toplevel,
762 int32_t width,
763 int32_t height,
764 struct wl_array *states)
765{
766 SDL_WindowData *wind = (SDL_WindowData *)data;
767 SDL_Window *window = wind->sdlwindow;
768
769 enum xdg_toplevel_state *state;
770 bool fullscreen = false;
771 bool maximized = false;
772 bool floating = true;
773 bool tiled = false;
774 bool active = false;
775 bool resizing = false;
776 bool suspended = false;
777 wl_array_for_each (state, states) {
778 switch (*state) {
779 case XDG_TOPLEVEL_STATE_FULLSCREEN:
780 fullscreen = true;
781 floating = false;
782 break;
783 case XDG_TOPLEVEL_STATE_MAXIMIZED:
784 maximized = true;
785 floating = false;
786 break;
787 case XDG_TOPLEVEL_STATE_RESIZING:
788 resizing = true;
789 break;
790 case XDG_TOPLEVEL_STATE_ACTIVATED:
791 active = true;
792 break;
793 case XDG_TOPLEVEL_STATE_TILED_LEFT:
794 case XDG_TOPLEVEL_STATE_TILED_RIGHT:
795 case XDG_TOPLEVEL_STATE_TILED_TOP:
796 case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
797 tiled = true;
798 floating = false;
799 break;
800 case XDG_TOPLEVEL_STATE_SUSPENDED:
801 suspended = true;
802 break;
803 default:
804 break;
805 }
806 }
807
808 UpdateWindowFullscreen(window, fullscreen);
809
810 /* Always send a maximized/restore event; if the event is redundant it will
811 * automatically be discarded (see src/events/SDL_windowevents.c)
812 *
813 * No, we do not get minimize events from xdg-shell, however, the minimized
814 * state can be programmatically set. The meaning of 'minimized' is compositor
815 * dependent, but in general, we can assume that the flag should remain set until
816 * the next focused configure event occurs.
817 */
818 if (active || !(window->flags & SDL_WINDOW_MINIMIZED)) {
819 if (window->flags & SDL_WINDOW_MINIMIZED) {
820 // If we were minimized, send a restored event before possibly sending maximized.
821 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
822 }
823 SDL_SendWindowEvent(window,
824 (maximized && !fullscreen) ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED,
825 0, 0);
826 }
827
828 if (!fullscreen) {
829 /* xdg_toplevel spec states that this is a suggestion.
830 * Ignore if less than or greater than max/min size.
831 */
832 if (window->flags & SDL_WINDOW_RESIZABLE) {
833 if (width == 0 || height == 0) {
834 /* This happens when the compositor indicates that the size is
835 * up to the client, so use the cached window size here.
836 */
837 if (floating) {
838 width = window->floating.w;
839 height = window->floating.h;
840
841 // Clamp the window to the toplevel bounds, if any are set.
842 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE &&
843 wind->toplevel_bounds.width && wind->toplevel_bounds.height) {
844 width = SDL_min(wind->toplevel_bounds.width, width);
845 height = SDL_min(wind->toplevel_bounds.height, height);
846 }
847 } else {
848 width = window->windowed.w;
849 height = window->windowed.h;
850 }
851
852 if (!wind->scale_to_display) {
853 wind->requested.logical_width = width;
854 wind->requested.logical_height = height;
855 } else {
856 wind->requested.pixel_width = width;
857 wind->requested.pixel_height = height;
858 width = wind->requested.logical_width = PixelToPoint(window, width);
859 height = wind->requested.logical_height = PixelToPoint(window, height);
860 }
861 } else {
862 /* Don't apply the supplied dimensions if they haven't changed from the last configuration
863 * event, or a newer size set programmatically can be overwritten by old data.
864 */
865 if (width != wind->last_configure.width || height != wind->last_configure.height) {
866 wind->requested.logical_width = width;
867 wind->requested.logical_height = height;
868
869 if (wind->scale_to_display) {
870 wind->requested.pixel_width = PointToPixel(window, width);
871 wind->requested.pixel_height = PointToPixel(window, height);
872 }
873 }
874 }
875 } else {
876 /* If we're a fixed-size window, we know our size for sure.
877 * Always assume the configure is wrong.
878 */
879 if (!wind->scale_to_display) {
880 width = wind->requested.logical_width = window->floating.w;
881 height = wind->requested.logical_height = window->floating.h;
882 } else {
883 wind->requested.pixel_width = window->floating.w;
884 wind->requested.pixel_height = window->floating.h;
885 width = wind->requested.logical_width = PixelToPoint(window, window->floating.w);
886 height = wind->requested.logical_height = PixelToPoint(window, window->floating.h);
887 }
888 }
889
890 /* Notes on the spec:
891 *
892 * - The content limits are only a hint, which the compositor is free to ignore,
893 * so apply them manually when appropriate.
894 *
895 * - Maximized windows must have their exact dimensions respected, thus they must
896 * not be resized, or a protocol violation can occur.
897 *
898 * - When resizing a window, the width/height are maximum values, so aspect ratio
899 * correction can't resize beyond the existing dimensions, or a protocol violation
900 * can occur. In practice, nothing seems to kill clients that do this, but doing
901 * so causes GNOME to glitch out.
902 */
903 if (!maximized) {
904 if (!wind->scale_to_display) {
905 if (window->max_w > 0) {
906 wind->requested.logical_width = SDL_min(wind->requested.logical_width, window->max_w);
907 }
908 wind->requested.logical_width = SDL_max(wind->requested.logical_width, window->min_w);
909
910 if (window->max_h > 0) {
911 wind->requested.logical_height = SDL_min(wind->requested.logical_height, window->max_h);
912 }
913 wind->requested.logical_height = SDL_max(wind->requested.logical_height, window->min_h);
914
915 // Aspect correction.
916 const float aspect = (float)wind->requested.logical_width / (float)wind->requested.logical_height;
917
918 if (window->min_aspect != 0.f && aspect < window->min_aspect) {
919 wind->requested.logical_height = SDL_lroundf((float)wind->requested.logical_width / window->min_aspect);
920 } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
921 wind->requested.logical_width = SDL_lroundf((float)wind->requested.logical_height * window->max_aspect);
922 }
923 } else {
924 if (window->max_w > 0) {
925 wind->requested.pixel_width = SDL_min(wind->requested.pixel_width, window->max_w);
926 }
927 wind->requested.pixel_width = SDL_max(wind->requested.pixel_width, window->min_w);
928
929 if (window->max_h > 0) {
930 wind->requested.pixel_height = SDL_min(wind->requested.pixel_height, window->max_h);
931 }
932 wind->requested.pixel_height = SDL_max(wind->requested.pixel_height, window->min_h);
933
934 // Aspect correction.
935 const float aspect = (float)wind->requested.pixel_width / (float)wind->requested.pixel_height;
936
937 if (window->min_aspect != 0.f && aspect < window->min_aspect) {
938 wind->requested.pixel_height = SDL_lroundf((float)wind->requested.pixel_width / window->min_aspect);
939 } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
940 wind->requested.pixel_width = SDL_lroundf((float)wind->requested.pixel_height * window->max_aspect);
941 }
942
943 wind->requested.logical_width = PixelToPoint(window, wind->requested.pixel_width);
944 wind->requested.logical_height = PixelToPoint(window, wind->requested.pixel_height);
945 }
946 }
947 } else {
948 // Fullscreen windows know their exact size.
949 if (width == 0 || height == 0) {
950 width = wind->requested.logical_width;
951 height = wind->requested.logical_height;
952 } else {
953 wind->requested.logical_width = width;
954 wind->requested.logical_height = height;
955 }
956
957 if (wind->scale_to_display) {
958 wind->requested.pixel_width = PointToPixel(window, width);
959 wind->requested.pixel_height = PointToPixel(window, height);
960 }
961 }
962
963 wind->last_configure.width = width;
964 wind->last_configure.height = height;
965 wind->floating = floating;
966 wind->suspended = suspended;
967 wind->active = active;
968 window->tiled = tiled;
969 wind->resizing = resizing;
970
971 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) {
972 wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME;
973 }
974}
975
976static void handle_close_xdg_toplevel(void *data, struct xdg_toplevel *xdg_toplevel)
977{
978 SDL_WindowData *window = (SDL_WindowData *)data;
979 SDL_SendWindowEvent(window->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
980}
981
982static void handle_xdg_configure_toplevel_bounds(void *data,
983 struct xdg_toplevel *xdg_toplevel,
984 int32_t width, int32_t height)
985{
986 SDL_WindowData *window = (SDL_WindowData *)data;
987 window->toplevel_bounds.width = width;
988 window->toplevel_bounds.height = height;
989}
990
991static void handle_xdg_toplevel_wm_capabilities(void *data,
992 struct xdg_toplevel *xdg_toplevel,
993 struct wl_array *capabilities)
994{
995 SDL_WindowData *wind = (SDL_WindowData *)data;
996 enum xdg_toplevel_wm_capabilities *wm_cap;
997
998 wind->wm_caps = 0;
999
1000 wl_array_for_each (wm_cap, capabilities) {
1001 switch (*wm_cap) {
1002 case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU:
1003 wind->wm_caps |= WAYLAND_WM_CAPS_WINDOW_MENU;
1004 break;
1005 case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE:
1006 wind->wm_caps |= WAYLAND_WM_CAPS_MAXIMIZE;
1007 break;
1008 case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN:
1009 wind->wm_caps |= WAYLAND_WM_CAPS_FULLSCREEN;
1010 break;
1011 case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE:
1012 wind->wm_caps |= WAYLAND_WM_CAPS_MINIMIZE;
1013 break;
1014 default:
1015 break;
1016 }
1017 }
1018}
1019
1020static const struct xdg_toplevel_listener toplevel_listener_xdg = {
1021 handle_configure_xdg_toplevel,
1022 handle_close_xdg_toplevel,
1023 handle_xdg_configure_toplevel_bounds, // Version 4
1024 handle_xdg_toplevel_wm_capabilities // Version 5
1025};
1026
1027static void handle_configure_xdg_popup(void *data,
1028 struct xdg_popup *xdg_popup,
1029 int32_t x,
1030 int32_t y,
1031 int32_t width,
1032 int32_t height)
1033{
1034 SDL_WindowData *wind = (SDL_WindowData *)data;
1035 int offset_x = 0, offset_y = 0;
1036
1037 // Adjust the position if it was offset for libdecor
1038 AdjustPopupOffset(wind->sdlwindow, &offset_x, &offset_y);
1039 x -= offset_x;
1040 y -= offset_y;
1041
1042 /* This happens when the compositor indicates that the size is
1043 * up to the client, so use the cached window size here.
1044 */
1045 if (width == 0 || height == 0) {
1046 width = wind->sdlwindow->floating.w;
1047 height = wind->sdlwindow->floating.h;
1048 }
1049
1050 /* Don't apply the supplied dimensions if they haven't changed from the last configuration
1051 * event, or a newer size set programmatically can be overwritten by old data.
1052 */
1053 if (width != wind->last_configure.width || height != wind->last_configure.height) {
1054 wind->requested.logical_width = width;
1055 wind->requested.logical_height = height;
1056
1057 if (wind->scale_to_display) {
1058 wind->requested.pixel_width = PointToPixel(wind->sdlwindow, width);
1059 wind->requested.pixel_height = PointToPixel(wind->sdlwindow, height);
1060 }
1061 }
1062
1063 if (wind->scale_to_display) {
1064 x = PointToPixel(wind->sdlwindow->parent, x);
1065 y = PointToPixel(wind->sdlwindow->parent, y);
1066 }
1067
1068 SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_MOVED, x, y);
1069
1070 wind->last_configure.width = width;
1071 wind->last_configure.height = height;
1072
1073 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) {
1074 wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME;
1075 }
1076}
1077
1078static void handle_done_xdg_popup(void *data, struct xdg_popup *xdg_popup)
1079{
1080 SDL_WindowData *window = (SDL_WindowData *)data;
1081 SDL_SendWindowEvent(window->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
1082}
1083
1084static void handle_repositioned_xdg_popup(void *data,
1085 struct xdg_popup *xdg_popup,
1086 uint32_t token)
1087{
1088 // No-op, configure does all the work we care about
1089}
1090
1091static const struct xdg_popup_listener popup_listener_xdg = {
1092 handle_configure_xdg_popup,
1093 handle_done_xdg_popup,
1094 handle_repositioned_xdg_popup
1095};
1096
1097static void handle_configure_zxdg_decoration(void *data,
1098 struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
1099 uint32_t mode)
1100{
1101 SDL_Window *window = (SDL_Window *)data;
1102 SDL_WindowData *internal = window->internal;
1103 SDL_VideoDevice *device = SDL_GetVideoDevice();
1104
1105 /* If the compositor tries to force CSD anyway, bail on direct XDG support
1106 * and fall back to libdecor, it will handle these events from then on.
1107 *
1108 * To do this we have to fully unmap, then map with libdecor loaded.
1109 */
1110 if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) {
1111 if (window->flags & SDL_WINDOW_BORDERLESS) {
1112 // borderless windows do request CSD, so we got what we wanted
1113 return;
1114 }
1115 if (!Wayland_LoadLibdecor(internal->waylandData, true)) {
1116 // libdecor isn't available, so no borders for you... oh well
1117 return;
1118 }
1119 WAYLAND_wl_display_roundtrip(internal->waylandData->display);
1120
1121 Wayland_HideWindow(device, window);
1122 SDL_zero(internal->shell_surface);
1123 internal->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR;
1124
1125 Wayland_ShowWindow(device, window);
1126 }
1127}
1128
1129static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = {
1130 handle_configure_zxdg_decoration
1131};
1132
1133#ifdef HAVE_LIBDECOR_H
1134/*
1135 * XXX: Hack for older versions of libdecor that lack the function to query the
1136 * minimum content size limit. The internal limits must always be overridden
1137 * to ensure that very small windows don't cause errors or crashes.
1138 *
1139 * On libdecor >= 0.1.2, which exposes the function to get the minimum content
1140 * size limit, this function is a no-op.
1141 *
1142 * Can be removed if the minimum required version of libdecor is raised to
1143 * 0.1.2 or higher.
1144 */
1145static void OverrideLibdecorLimits(SDL_Window *window)
1146{
1147#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR
1148 if (!libdecor_frame_get_min_content_size) {
1149 libdecor_frame_set_min_content_size(window->internal->shell_surface.libdecor.frame, window->min_w, window->min_h);
1150 }
1151#elif !SDL_LIBDECOR_CHECK_VERSION(0, 2, 0)
1152 libdecor_frame_set_min_content_size(window->internal->shell_surface.libdecor.frame, window->min_w, window->min_h);
1153#endif
1154}
1155
1156/*
1157 * NOTE: Retrieves the minimum content size limits, if the function for doing so is available.
1158 * On versions of libdecor that lack the minimum content size retrieval function, this
1159 * function is a no-op.
1160 *
1161 * Can be replaced with a direct call if the minimum required version of libdecor is raised
1162 * to 0.1.2 or higher.
1163 */
1164static void LibdecorGetMinContentSize(struct libdecor_frame *frame, int *min_w, int *min_h)
1165{
1166#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR
1167 if (libdecor_frame_get_min_content_size != NULL) {
1168 libdecor_frame_get_min_content_size(frame, min_w, min_h);
1169 }
1170#elif SDL_LIBDECOR_CHECK_VERSION(0, 2, 0)
1171 libdecor_frame_get_min_content_size(frame, min_w, min_h);
1172#endif
1173}
1174
1175static void decoration_frame_configure(struct libdecor_frame *frame,
1176 struct libdecor_configuration *configuration,
1177 void *user_data)
1178{
1179 SDL_WindowData *wind = (SDL_WindowData *)user_data;
1180 SDL_Window *window = wind->sdlwindow;
1181
1182 enum libdecor_window_state window_state;
1183 int width, height;
1184
1185 bool prev_fullscreen = wind->is_fullscreen;
1186 bool active = false;
1187 bool fullscreen = false;
1188 bool maximized = false;
1189 bool tiled = false;
1190 bool suspended = false;
1191 bool resizing = false;
1192
1193 static const enum libdecor_window_state tiled_states = (LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT |
1194 LIBDECOR_WINDOW_STATE_TILED_TOP | LIBDECOR_WINDOW_STATE_TILED_BOTTOM);
1195
1196 // Window State
1197 if (libdecor_configuration_get_window_state(configuration, &window_state)) {
1198 fullscreen = (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0;
1199 maximized = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) != 0;
1200 active = (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) != 0;
1201 tiled = (window_state & tiled_states) != 0;
1202#if SDL_LIBDECOR_CHECK_VERSION(0, 2, 0)
1203 suspended = (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) != 0;
1204#endif
1205#if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0)
1206 resizing = (window_state & LIBDECOR_WINDOW_STATE_RESIZING) != 0;
1207#endif
1208 }
1209 const bool floating = !(fullscreen || maximized || tiled);
1210
1211 UpdateWindowFullscreen(window, fullscreen);
1212
1213 /* Always send a maximized/restore event; if the event is redundant it will
1214 * automatically be discarded (see src/events/SDL_windowevents.c)
1215 *
1216 * No, we do not get minimize events from libdecor, however, the minimized
1217 * state can be programmatically set. The meaning of 'minimized' is compositor
1218 * dependent, but in general, we can assume that the flag should remain set until
1219 * the next focused configure event occurs.
1220 */
1221 if (active || !(window->flags & SDL_WINDOW_MINIMIZED)) {
1222 if (window->flags & SDL_WINDOW_MINIMIZED) {
1223 // If we were minimized, send a restored event before possibly sending maximized.
1224 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
1225 }
1226 SDL_SendWindowEvent(window,
1227 (maximized && !fullscreen) ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED,
1228 0, 0);
1229 }
1230
1231 /* For fullscreen or fixed-size windows we know our size.
1232 * Always assume the configure is wrong.
1233 */
1234 if (fullscreen) {
1235 if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
1236 width = wind->requested.logical_width;
1237 height = wind->requested.logical_height;
1238 } else {
1239 // Fullscreen windows know their exact size.
1240 wind->requested.logical_width = width;
1241 wind->requested.logical_height = height;
1242
1243 if (wind->scale_to_display) {
1244 wind->requested.pixel_width = PointToPixel(window, width);
1245 wind->requested.pixel_height = PointToPixel(window, height);
1246 }
1247 }
1248 } else {
1249 if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
1250 /* If we're a fixed-size window, we know our size for sure.
1251 * Always assume the configure is wrong.
1252 */
1253 if (!wind->scale_to_display) {
1254 width = wind->requested.logical_width = window->floating.w;
1255 height = wind->requested.logical_height = window->floating.h;
1256 } else {
1257 wind->requested.pixel_width = window->floating.w;
1258 wind->requested.pixel_height = window->floating.h;
1259 width = wind->requested.logical_width = PixelToPoint(window, window->floating.w);
1260 height = wind->requested.logical_height = PixelToPoint(window, window->floating.h);
1261 }
1262
1263 OverrideLibdecorLimits(window);
1264 } else {
1265 /* XXX: The libdecor cairo plugin sends bogus content sizes that add the
1266 * height of the title bar when transitioning from a fixed-size to
1267 * floating state. Ignore the sent window dimensions in this case,
1268 * in favor of the cached value to avoid the window increasing in
1269 * size after every state transition.
1270 *
1271 * https://gitlab.freedesktop.org/libdecor/libdecor/-/issues/34
1272 */
1273 if ((floating && (!wind->floating && !(window->flags & SDL_WINDOW_BORDERLESS))) ||
1274 !libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
1275 /* This happens when we're being restored from a non-floating state,
1276 * or the compositor indicates that the size is up to the client, so
1277 * used the cached window size here.
1278 */
1279 if (floating) {
1280 width = window->floating.w;
1281 height = window->floating.h;
1282 } else {
1283 width = window->windowed.w;
1284 height = window->windowed.h;
1285 }
1286
1287 if (!wind->scale_to_display) {
1288 wind->requested.logical_width = width;
1289 wind->requested.logical_height = height;
1290 } else {
1291 wind->requested.pixel_width = width;
1292 wind->requested.pixel_height = height;
1293 width = wind->requested.logical_width = PixelToPoint(window, width);
1294 height = wind->requested.logical_height = PixelToPoint(window, height);
1295 }
1296 } else {
1297 /* Don't apply the supplied dimensions if they haven't changed from the last configuration
1298 * event, or a newer size set programmatically can be overwritten by old data.
1299 */
1300 if (width != wind->last_configure.width || height != wind->last_configure.height) {
1301 wind->requested.logical_width = width;
1302 wind->requested.logical_height = height;
1303
1304 if (wind->scale_to_display) {
1305 wind->requested.pixel_width = PointToPixel(window, width);
1306 wind->requested.pixel_height = PointToPixel(window, height);
1307 }
1308 }
1309 }
1310 }
1311
1312 /* Notes on the spec:
1313 *
1314 * - The content limits are only a hint, which the compositor is free to ignore,
1315 * so apply them manually when appropriate.
1316 *
1317 * - Maximized windows must have their exact dimensions respected, thus they must
1318 * not be resized, or a protocol violation can occur.
1319 *
1320 * - When resizing a window, the width/height are maximum values, so aspect ratio
1321 * correction can't resize beyond the existing dimensions, or a protocol violation
1322 * can occur. In practice, nothing seems to kill clients that do this, but doing
1323 * so causes GNOME to glitch out.
1324 */
1325 if (!maximized) {
1326 if (!wind->scale_to_display) {
1327 if (window->max_w > 0) {
1328 wind->requested.logical_width = SDL_min(wind->requested.logical_width, window->max_w);
1329 }
1330 wind->requested.logical_width = SDL_max(wind->requested.logical_width, window->min_w);
1331
1332 if (window->max_h > 0) {
1333 wind->requested.logical_height = SDL_min(wind->requested.logical_height, window->max_h);
1334 }
1335 wind->requested.logical_height = SDL_max(wind->requested.logical_height, window->min_h);
1336
1337 // Aspect correction.
1338 const float aspect = (float)wind->requested.logical_width / (float)wind->requested.logical_height;
1339
1340 if (window->min_aspect != 0.f && aspect < window->min_aspect) {
1341 wind->requested.logical_height = SDL_lroundf((float)wind->requested.logical_width / window->min_aspect);
1342 } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
1343 wind->requested.logical_width = SDL_lroundf((float)wind->requested.logical_height * window->max_aspect);
1344 }
1345 } else {
1346 if (window->max_w > 0) {
1347 wind->requested.pixel_width = SDL_min(wind->requested.pixel_width, window->max_w);
1348 }
1349 wind->requested.pixel_width = SDL_max(wind->requested.pixel_width, window->min_w);
1350
1351 if (window->max_h > 0) {
1352 wind->requested.pixel_height = SDL_min(wind->requested.pixel_height, window->max_h);
1353 }
1354 wind->requested.pixel_height = SDL_max(wind->requested.pixel_height, window->min_h);
1355
1356 // Aspect correction.
1357 const float aspect = (float)wind->requested.pixel_width / (float)wind->requested.pixel_height;
1358
1359 if (window->min_aspect != 0.f && aspect < window->min_aspect) {
1360 wind->requested.pixel_height = SDL_lroundf((float)wind->requested.pixel_width / window->min_aspect);
1361 } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
1362 wind->requested.pixel_width = SDL_lroundf((float)wind->requested.pixel_height * window->max_aspect);
1363 }
1364
1365 wind->requested.logical_width = PixelToPoint(window, wind->requested.pixel_width);
1366 wind->requested.logical_height = PixelToPoint(window, wind->requested.pixel_height);
1367 }
1368 }
1369 }
1370
1371 // Store the new state.
1372 wind->last_configure.width = width;
1373 wind->last_configure.height = height;
1374 wind->floating = floating;
1375 wind->suspended = suspended;
1376 wind->active = active;
1377 window->tiled = tiled;
1378 wind->resizing = resizing;
1379
1380 // Update the window manager capabilities.
1381#if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0)
1382 enum libdecor_wm_capabilities caps;
1383#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR
1384 if (libdecor_frame_get_wm_capabilities) {
1385 caps = libdecor_frame_get_wm_capabilities(wind->shell_surface.libdecor.frame);
1386#else
1387 caps = libdecor_frame_get_wm_capabilities(wind->shell_surface.libdecor.frame);
1388 {
1389#endif
1390 wind->wm_caps = 0;
1391 wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_WINDOW_MENU ? WAYLAND_WM_CAPS_WINDOW_MENU : 0;
1392 wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_MAXIMIZE ? WAYLAND_WM_CAPS_MAXIMIZE : 0;
1393 wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_FULLSCREEN ? WAYLAND_WM_CAPS_FULLSCREEN : 0;
1394 wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_MINIMIZE ? WAYLAND_WM_CAPS_MINIMIZE : 0;
1395 }
1396#endif
1397
1398 // Calculate the new window geometry
1399 if (ConfigureWindowGeometry(window)) {
1400 // ... then commit the changes on the libdecor side.
1401 struct libdecor_state *state = libdecor_state_new(wind->current.logical_width, wind->current.logical_height);
1402 libdecor_frame_commit(frame, state, configuration);
1403 libdecor_state_free(state);
1404 }
1405
1406 if (!wind->shell_surface.libdecor.initial_configure_seen) {
1407 LibdecorGetMinContentSize(frame, &wind->system_limits.min_width, &wind->system_limits.min_height);
1408 wind->shell_surface.libdecor.initial_configure_seen = true;
1409 }
1410 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) {
1411 wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME;
1412 }
1413
1414 /* Update the resize capability if this config event was the result of the
1415 * compositor taking a window out of fullscreen. Since this will change the
1416 * capabilities and commit a new frame state with the last known content
1417 * dimension, this has to be called after the new state has been committed
1418 * and the new content dimensions were updated.
1419 */
1420 if (prev_fullscreen && !wind->is_fullscreen) {
1421 Wayland_SetWindowResizable(SDL_GetVideoDevice(), window,
1422 !!(window->flags & SDL_WINDOW_RESIZABLE));
1423 }
1424}
1425
1426static void decoration_frame_close(struct libdecor_frame *frame, void *user_data)
1427{
1428 SDL_SendWindowEvent(((SDL_WindowData *)user_data)->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
1429}
1430
1431static void decoration_frame_commit(struct libdecor_frame *frame, void *user_data)
1432{
1433 /* libdecor decoration subsurfaces are synchronous, so the client needs to
1434 * commit a frame to trigger an update of the decoration surfaces.
1435 */
1436 SDL_WindowData *wind = (SDL_WindowData *)user_data;
1437 if (!wind->suspended && wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOWN) {
1438 SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
1439 }
1440}
1441
1442static void decoration_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data)
1443{
1444 // NOP
1445}
1446
1447static struct libdecor_frame_interface libdecor_frame_interface = {
1448 decoration_frame_configure,
1449 decoration_frame_close,
1450 decoration_frame_commit,
1451 decoration_dismiss_popup
1452};
1453#endif
1454
1455static void Wayland_HandlePreferredScaleChanged(SDL_WindowData *window_data, double factor)
1456{
1457 const double old_factor = window_data->scale_factor;
1458
1459 // Round the scale factor if viewports aren't available.
1460 if (!window_data->viewport) {
1461 factor = SDL_ceil(factor);
1462 }
1463
1464 if (factor != old_factor) {
1465 window_data->scale_factor = factor;
1466
1467 if (window_data->scale_to_display) {
1468 /* If the window is in the floating state with a user/application specified size, calculate the new
1469 * logical size from the backbuffer size. Otherwise, use the fixed underlying logical size to calculate
1470 * the new backbuffer dimensions.
1471 */
1472 if (window_data->floating) {
1473 window_data->requested.logical_width = PixelToPoint(window_data->sdlwindow, window_data->requested.pixel_width);
1474 window_data->requested.logical_height = PixelToPoint(window_data->sdlwindow, window_data->requested.pixel_height);
1475 } else {
1476 window_data->requested.pixel_width = PointToPixel(window_data->sdlwindow, window_data->requested.logical_width);
1477 window_data->requested.pixel_height = PointToPixel(window_data->sdlwindow, window_data->requested.logical_height);
1478 }
1479 }
1480
1481 if (window_data->sdlwindow->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY || window_data->scale_to_display) {
1482 ConfigureWindowGeometry(window_data->sdlwindow);
1483 CommitLibdecorFrame(window_data->sdlwindow);
1484 }
1485 }
1486}
1487
1488static void Wayland_MaybeUpdateScaleFactor(SDL_WindowData *window)
1489{
1490 double factor;
1491 int i;
1492
1493 /* If the fractional scale protocol is present or the core protocol supports the
1494 * preferred buffer scale event, the compositor will explicitly tell the application
1495 * what scale it wants via these events, so don't try to determine the scale factor
1496 * from which displays the surface has entered.
1497 */
1498 if (window->fractional_scale || wl_surface_get_version(window->surface) >= WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) {
1499 return;
1500 }
1501
1502 if (window->num_outputs != 0) {
1503 // Check every display's factor, use the highest
1504 factor = 0.0;
1505 for (i = 0; i < window->num_outputs; i++) {
1506 SDL_DisplayData *internal = window->outputs[i];
1507 factor = SDL_max(factor, internal->scale_factor);
1508 }
1509 } else {
1510 // All outputs removed, just fall back.
1511 factor = window->scale_factor;
1512 }
1513
1514 Wayland_HandlePreferredScaleChanged(window, factor);
1515}
1516
1517void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, SDL_DisplayData *display_data)
1518{
1519 for (int i = 0; i < window->num_outputs; i++) {
1520 if (window->outputs[i] == display_data) { // remove this one
1521 if (i == (window->num_outputs - 1)) {
1522 window->outputs[i] = NULL;
1523 } else {
1524 SDL_memmove(&window->outputs[i],
1525 &window->outputs[i + 1],
1526 sizeof(SDL_DisplayData *) * ((window->num_outputs - i) - 1));
1527 }
1528 window->num_outputs--;
1529 i--;
1530 }
1531 }
1532
1533 if (window->num_outputs == 0) {
1534 SDL_free(window->outputs);
1535 window->outputs = NULL;
1536 } else if (!window->is_fullscreen || window->num_outputs == 1) {
1537 Wayland_move_window(window->sdlwindow);
1538 Wayland_MaybeUpdateScaleFactor(window);
1539 }
1540}
1541
1542static void handle_surface_enter(void *data, struct wl_surface *surface, struct wl_output *output)
1543{
1544 SDL_WindowData *window = data;
1545 SDL_DisplayData *internal = wl_output_get_user_data(output);
1546 SDL_DisplayData **new_outputs;
1547
1548 if (!SDL_WAYLAND_own_output(output) || !SDL_WAYLAND_own_surface(surface)) {
1549 return;
1550 }
1551
1552 new_outputs = SDL_realloc(window->outputs,
1553 sizeof(SDL_DisplayData *) * (window->num_outputs + 1));
1554 if (!new_outputs) {
1555 return;
1556 }
1557 window->outputs = new_outputs;
1558 window->outputs[window->num_outputs++] = internal;
1559
1560 // Update the scale factor after the move so that fullscreen outputs are updated.
1561 if (!window->is_fullscreen || window->num_outputs == 1) {
1562 Wayland_move_window(window->sdlwindow);
1563 Wayland_MaybeUpdateScaleFactor(window);
1564 }
1565}
1566
1567static void handle_surface_leave(void *data, struct wl_surface *surface, struct wl_output *output)
1568{
1569 SDL_WindowData *window = (SDL_WindowData *)data;
1570
1571 if (!SDL_WAYLAND_own_output(output) || !SDL_WAYLAND_own_surface(surface)) {
1572 return;
1573 }
1574
1575 Wayland_RemoveOutputFromWindow(window, (SDL_DisplayData *)wl_output_get_user_data(output));
1576}
1577
1578static void handle_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor)
1579{
1580 SDL_WindowData *wind = data;
1581
1582 /* The spec is unclear on how this interacts with the fractional scaling protocol,
1583 * so, for now, assume that the fractional scaling protocol takes priority and
1584 * only listen to this event if the fractional scaling protocol is not present.
1585 */
1586 if (!wind->fractional_scale) {
1587 Wayland_HandlePreferredScaleChanged(data, (double)factor);
1588 }
1589}
1590
1591static void handle_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform)
1592{
1593 // Nothing to do here.
1594}
1595
1596static const struct wl_surface_listener surface_listener = {
1597 handle_surface_enter,
1598 handle_surface_leave,
1599 handle_preferred_buffer_scale,
1600 handle_preferred_buffer_transform
1601};
1602
1603static void handle_preferred_fractional_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale)
1604{
1605 const double factor = (double)scale / 120.; // 120 is a magic number defined in the spec as a common denominator
1606 Wayland_HandlePreferredScaleChanged(data, factor);
1607}
1608
1609static const struct wp_fractional_scale_v1_listener fractional_scale_listener = {
1610 handle_preferred_fractional_scale
1611};
1612
1613static void frog_preferred_metadata_handler(void *data, struct frog_color_managed_surface *frog_color_managed_surface, uint32_t transfer_function,
1614 uint32_t output_display_primary_red_x, uint32_t output_display_primary_red_y,
1615 uint32_t output_display_primary_green_x, uint32_t output_display_primary_green_y,
1616 uint32_t output_display_primary_blue_x, uint32_t output_display_primary_blue_y,
1617 uint32_t output_white_point_x, uint32_t output_white_point_y,
1618 uint32_t max_luminance, uint32_t min_luminance,
1619 uint32_t max_full_frame_luminance)
1620{
1621 SDL_WindowData *wind = (SDL_WindowData *)data;
1622 SDL_HDROutputProperties HDR;
1623
1624 SDL_zero(HDR);
1625
1626 switch (transfer_function) {
1627 case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ:
1628 /* ITU-R BT.2408-7 (Sept 2023) has the reference PQ white level at 203 nits,
1629 * while older Dolby documentation claims a reference level of 100 nits.
1630 *
1631 * Use 203 nits for now.
1632 */
1633 HDR.HDR_headroom = max_luminance / 203.0f;
1634 break;
1635 case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SCRGB_LINEAR:
1636 HDR.HDR_headroom = max_luminance / 80.0f;
1637 break;
1638 case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_UNDEFINED:
1639 case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SRGB:
1640 case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_GAMMA_22:
1641 default:
1642 HDR.HDR_headroom = 1.0f;
1643 break;
1644 }
1645
1646 HDR.SDR_white_level = 1.0f;
1647 SDL_SetWindowHDRProperties(wind->sdlwindow, &HDR, true);
1648}
1649
1650static const struct frog_color_managed_surface_listener frog_surface_listener = {
1651 frog_preferred_metadata_handler
1652};
1653
1654static void feedback_surface_preferred_changed(void *data,
1655 struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback_v1,
1656 uint32_t identity)
1657{
1658 SDL_WindowData *wind = (SDL_WindowData *)data;
1659 Wayland_GetColorInfoForWindow(wind, false);
1660}
1661
1662static const struct wp_color_management_surface_feedback_v1_listener color_management_surface_feedback_listener = {
1663 feedback_surface_preferred_changed
1664};
1665
1666static void SetKeyboardFocus(SDL_Window *window, bool set_focus)
1667{
1668 SDL_Window *toplevel = window;
1669
1670 // Find the toplevel parent
1671 while (SDL_WINDOW_IS_POPUP(toplevel)) {
1672 toplevel = toplevel->parent;
1673 }
1674
1675 toplevel->internal->keyboard_focus = window;
1676
1677 if (set_focus && !window->is_hiding && !window->is_destroying) {
1678 SDL_SetKeyboardFocus(window);
1679 }
1680}
1681
1682bool Wayland_SetWindowHitTest(SDL_Window *window, bool enabled)
1683{
1684 return true; // just succeed, the real work is done elsewhere.
1685}
1686
1687static struct xdg_toplevel *GetToplevelForWindow(SDL_WindowData *wind)
1688{
1689 if (wind) {
1690 /* Libdecor crashes on attempts to unset the parent by passing null, which is allowed by the
1691 * toplevel spec, so just use the raw xdg-toplevel instead (that's what libdecor does
1692 * internally anyways).
1693 */
1694#ifdef HAVE_LIBDECOR_H
1695 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) {
1696 return libdecor_frame_get_xdg_toplevel(wind->shell_surface.libdecor.frame);
1697 } else
1698#endif
1699 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && wind->shell_surface.xdg.toplevel.xdg_toplevel) {
1700 return wind->shell_surface.xdg.toplevel.xdg_toplevel;
1701 }
1702 }
1703
1704 return NULL;
1705}
1706
1707bool Wayland_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent_window)
1708{
1709 SDL_WindowData *child_data = window->internal;
1710 SDL_WindowData *parent_data = parent_window ? parent_window->internal : NULL;
1711
1712 child_data->reparenting_required = false;
1713
1714 if (parent_data && parent_data->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) {
1715 // Need to wait for the parent to become mapped, or it's the same as setting a null parent.
1716 child_data->reparenting_required = true;
1717 return true;
1718 }
1719
1720 struct xdg_toplevel *child_toplevel = GetToplevelForWindow(child_data);
1721 struct xdg_toplevel *parent_toplevel = GetToplevelForWindow(parent_data);
1722
1723 if (child_toplevel) {
1724 xdg_toplevel_set_parent(child_toplevel, parent_toplevel);
1725 }
1726
1727 return true;
1728}
1729
1730bool Wayland_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal)
1731{
1732 SDL_VideoData *viddata = _this->internal;
1733 SDL_WindowData *data = window->internal;
1734 SDL_WindowData *parent_data = window->parent->internal;
1735
1736 if (parent_data->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) {
1737 // Need to wait for the parent to become mapped before changing modal status.
1738 data->reparenting_required = true;
1739 return true;
1740 } else {
1741 data->reparenting_required = false;
1742 }
1743
1744 struct xdg_toplevel *toplevel = GetToplevelForWindow(data);
1745
1746 if (toplevel) {
1747 if (viddata->xdg_wm_dialog_v1) {
1748 if (modal) {
1749 if (!data->xdg_dialog_v1) {
1750 data->xdg_dialog_v1 = xdg_wm_dialog_v1_get_xdg_dialog(viddata->xdg_wm_dialog_v1, toplevel);
1751 }
1752
1753 xdg_dialog_v1_set_modal(data->xdg_dialog_v1);
1754 } else if (data->xdg_dialog_v1) {
1755 xdg_dialog_v1_unset_modal(data->xdg_dialog_v1);
1756 }
1757 }
1758 }
1759
1760 return true;
1761}
1762
1763static void show_hide_sync_handler(void *data, struct wl_callback *callback, uint32_t callback_data)
1764{
1765 // Get the window from the ID as it may have been destroyed
1766 SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data);
1767 SDL_Window *window = SDL_GetWindowFromID(windowID);
1768
1769 if (window && window->internal) {
1770 SDL_WindowData *wind = window->internal;
1771 wind->show_hide_sync_required = false;
1772 }
1773
1774 wl_callback_destroy(callback);
1775}
1776
1777static struct wl_callback_listener show_hide_sync_listener = {
1778 show_hide_sync_handler
1779};
1780
1781static void exported_handle_handler(void *data, struct zxdg_exported_v2 *zxdg_exported_v2, const char *handle)
1782{
1783 SDL_WindowData *wind = (SDL_WindowData*)data;
1784 SDL_PropertiesID props = SDL_GetWindowProperties(wind->sdlwindow);
1785
1786 SDL_SetStringProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, handle);
1787}
1788
1789static struct zxdg_exported_v2_listener exported_v2_listener = {
1790 exported_handle_handler
1791};
1792
1793void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
1794{
1795 SDL_VideoData *c = _this->internal;
1796 SDL_WindowData *data = window->internal;
1797 SDL_PropertiesID props = SDL_GetWindowProperties(window);
1798
1799 // Custom surfaces don't get toplevels and are always considered 'shown'; nothing to do here.
1800 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) {
1801 return;
1802 }
1803
1804 /* If this is a child window, the parent *must* be in the final shown state,
1805 * meaning that it has received a configure event, followed by a frame callback.
1806 * If not, a race condition can result, with effects ranging from the child
1807 * window to spuriously closing to protocol errors.
1808 *
1809 * If waiting on the parent window, set the pending status and the window will
1810 * be shown when the parent is in the shown state.
1811 */
1812 if (window->parent) {
1813 if (window->parent->internal->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) {
1814 data->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_SHOW_PENDING;
1815 return;
1816 }
1817 }
1818
1819 /* The window was hidden, but the sync point hasn't yet been reached.
1820 * Pump events to avoid a possible protocol violation.
1821 */
1822 if (data->show_hide_sync_required) {
1823 WAYLAND_wl_display_roundtrip(c->display);
1824 }
1825
1826 data->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE;
1827
1828 /* Detach any previous buffers before resetting everything, otherwise when
1829 * calling this a second time you'll get an annoying protocol error!
1830 *
1831 * FIXME: This was originally moved to HideWindow, which _should_ make
1832 * sense, but for whatever reason UE5's popups require that this actually
1833 * be in both places at once? Possibly from renderers making commits? I can't
1834 * fully remember if this location caused crashes or if I was fixing a pair
1835 * of Hide/Show calls. In any case, UE gives us a pretty good test and having
1836 * both detach calls passes. This bug may be relevant if I'm wrong:
1837 *
1838 * https://bugs.kde.org/show_bug.cgi?id=448856
1839 *
1840 * -flibit
1841 */
1842 wl_surface_attach(data->surface, NULL, 0, 0);
1843 wl_surface_commit(data->surface);
1844
1845 // Create the shell surface and map the toplevel/popup
1846#ifdef HAVE_LIBDECOR_H
1847 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
1848 data->shell_surface.libdecor.frame = libdecor_decorate(c->shell.libdecor,
1849 data->surface,
1850 &libdecor_frame_interface,
1851 data);
1852 if (!data->shell_surface.libdecor.frame) {
1853 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Failed to create libdecor frame!");
1854 } else {
1855 libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, data->app_id);
1856 libdecor_frame_map(data->shell_surface.libdecor.frame);
1857 if (window->flags & SDL_WINDOW_BORDERLESS) {
1858 // Note: Calling this with 'true' immediately after mapping will cause the libdecor Cairo plugin to crash.
1859 libdecor_frame_set_visibility(data->shell_surface.libdecor.frame, false);
1860 }
1861
1862 if (c->zxdg_exporter_v2) {
1863 data->exported = zxdg_exporter_v2_export_toplevel(c->zxdg_exporter_v2, data->surface);
1864 zxdg_exported_v2_add_listener(data->exported, &exported_v2_listener, data);
1865 }
1866
1867 if (c->xdg_toplevel_icon_manager_v1 && data->xdg_toplevel_icon_v1) {
1868 xdg_toplevel_icon_manager_v1_set_icon(_this->internal->xdg_toplevel_icon_manager_v1,
1869 libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame),
1870 data->xdg_toplevel_icon_v1);
1871 }
1872
1873 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, libdecor_frame_get_xdg_surface(data->shell_surface.libdecor.frame));
1874 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame));
1875 }
1876 } else
1877#endif
1878 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL || data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) {
1879 data->shell_surface.xdg.surface = xdg_wm_base_get_xdg_surface(c->shell.xdg, data->surface);
1880 xdg_surface_set_user_data(data->shell_surface.xdg.surface, data);
1881 xdg_surface_add_listener(data->shell_surface.xdg.surface, &shell_surface_listener_xdg, data);
1882 SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, data->shell_surface.xdg.surface);
1883
1884 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) {
1885 SDL_Window *parent = window->parent;
1886 SDL_WindowData *parent_data = parent->internal;
1887 struct xdg_surface *parent_xdg_surface = NULL;
1888 int position_x = 0, position_y = 0;
1889
1890 // Configure the popup parameters
1891#ifdef HAVE_LIBDECOR_H
1892 if (parent_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
1893 parent_xdg_surface = libdecor_frame_get_xdg_surface(parent_data->shell_surface.libdecor.frame);
1894 } else
1895#endif
1896 if (parent_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL ||
1897 parent_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) {
1898 parent_xdg_surface = parent_data->shell_surface.xdg.surface;
1899 }
1900
1901 // Set up the positioner for the popup and configure the constraints
1902 data->shell_surface.xdg.popup.xdg_positioner = xdg_wm_base_create_positioner(c->shell.xdg);
1903 xdg_positioner_set_anchor(data->shell_surface.xdg.popup.xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
1904 xdg_positioner_set_anchor_rect(data->shell_surface.xdg.popup.xdg_positioner, 0, 0, parent->internal->current.logical_width, parent->internal->current.logical_width);
1905 xdg_positioner_set_constraint_adjustment(data->shell_surface.xdg.popup.xdg_positioner,
1906 XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
1907 xdg_positioner_set_gravity(data->shell_surface.xdg.popup.xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
1908 xdg_positioner_set_size(data->shell_surface.xdg.popup.xdg_positioner, data->current.logical_width, data->current.logical_height);
1909
1910 // Set the popup initial position
1911 position_x = window->last_position_pending ? window->pending.x : window->x;
1912 position_y = window->last_position_pending ? window->pending.y : window->y;
1913 EnsurePopupPositionIsValid(window, &position_x, &position_y);
1914 if (data->scale_to_display) {
1915 position_x = PixelToPoint(window->parent, position_x);
1916 position_y = PixelToPoint(window->parent, position_y);
1917 }
1918 AdjustPopupOffset(window, &position_x, &position_y);
1919 xdg_positioner_set_offset(data->shell_surface.xdg.popup.xdg_positioner, position_x, position_y);
1920
1921 // Assign the popup role
1922 data->shell_surface.xdg.popup.xdg_popup = xdg_surface_get_popup(data->shell_surface.xdg.surface,
1923 parent_xdg_surface,
1924 data->shell_surface.xdg.popup.xdg_positioner);
1925 xdg_popup_add_listener(data->shell_surface.xdg.popup.xdg_popup, &popup_listener_xdg, data);
1926
1927 if (window->flags & SDL_WINDOW_TOOLTIP) {
1928 struct wl_region *region;
1929
1930 // Tooltips can't be interacted with, so turn off the input region to avoid blocking anything behind them
1931 region = wl_compositor_create_region(c->compositor);
1932 wl_region_add(region, 0, 0, 0, 0);
1933 wl_surface_set_input_region(data->surface, region);
1934 wl_region_destroy(region);
1935 } else if (window->flags & SDL_WINDOW_POPUP_MENU) {
1936 SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus());
1937 }
1938
1939 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POPUP_POINTER, data->shell_surface.xdg.popup.xdg_popup);
1940 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POSITIONER_POINTER, data->shell_surface.xdg.popup.xdg_positioner);
1941 } else {
1942 data->shell_surface.xdg.toplevel.xdg_toplevel = xdg_surface_get_toplevel(data->shell_surface.xdg.surface);
1943 xdg_toplevel_set_app_id(data->shell_surface.xdg.toplevel.xdg_toplevel, data->app_id);
1944 xdg_toplevel_add_listener(data->shell_surface.xdg.toplevel.xdg_toplevel, &toplevel_listener_xdg, data);
1945
1946 // Create the window decorations
1947 if (c->decoration_manager) {
1948 data->server_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(c->decoration_manager, data->shell_surface.xdg.toplevel.xdg_toplevel);
1949 zxdg_toplevel_decoration_v1_add_listener(data->server_decoration, &decoration_listener, window);
1950 const enum zxdg_toplevel_decoration_v1_mode mode = !(window->flags & SDL_WINDOW_BORDERLESS) ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
1951 zxdg_toplevel_decoration_v1_set_mode(data->server_decoration, mode);
1952 }
1953
1954 if (c->zxdg_exporter_v2) {
1955 data->exported = zxdg_exporter_v2_export_toplevel(c->zxdg_exporter_v2, data->surface);
1956 zxdg_exported_v2_add_listener(data->exported, &exported_v2_listener, data);
1957 }
1958
1959 if (c->xdg_toplevel_icon_manager_v1 && data->xdg_toplevel_icon_v1) {
1960 xdg_toplevel_icon_manager_v1_set_icon(_this->internal->xdg_toplevel_icon_manager_v1,
1961 data->shell_surface.xdg.toplevel.xdg_toplevel,
1962 data->xdg_toplevel_icon_v1);
1963 }
1964
1965 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, data->shell_surface.xdg.toplevel.xdg_toplevel);
1966 }
1967 }
1968
1969 // Restore state that was set prior to this call
1970 Wayland_SetWindowParent(_this, window, window->parent);
1971
1972 if (window->flags & SDL_WINDOW_MODAL) {
1973 Wayland_SetWindowModal(_this, window, true);
1974 }
1975
1976 Wayland_SetWindowTitle(_this, window);
1977
1978 /* We have to wait until the surface gets a "configure" event, or use of
1979 * this surface will fail. This is a new rule for xdg_shell.
1980 */
1981#ifdef HAVE_LIBDECOR_H
1982 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
1983 if (data->shell_surface.libdecor.frame) {
1984 while (!data->shell_surface.libdecor.initial_configure_seen) {
1985 WAYLAND_wl_display_flush(c->display);
1986 WAYLAND_wl_display_dispatch(c->display);
1987 }
1988 }
1989 } else
1990#endif
1991 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP || data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
1992 /* Unlike libdecor we need to call this explicitly to prevent a deadlock.
1993 * libdecor will call this as part of their configure event!
1994 * -flibit
1995 */
1996 wl_surface_commit(data->surface);
1997 if (data->shell_surface.xdg.surface) {
1998 while (!data->shell_surface.xdg.initial_configure_seen) {
1999 WAYLAND_wl_display_flush(c->display);
2000 WAYLAND_wl_display_dispatch(c->display);
2001 }
2002 }
2003 } else {
2004 // Nothing to see here, just commit.
2005 wl_surface_commit(data->surface);
2006 }
2007
2008 // Make sure the window can't be resized to 0 or it can be spuriously closed by the window manager.
2009 data->system_limits.min_width = SDL_max(data->system_limits.min_width, 1);
2010 data->system_limits.min_height = SDL_max(data->system_limits.min_height, 1);
2011
2012 /* Unlike the rest of window state we have to set this _after_ flushing the
2013 * display, because we need to create the decorations before possibly hiding
2014 * them immediately afterward.
2015 */
2016#ifdef HAVE_LIBDECOR_H
2017 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
2018 // Libdecor plugins can enforce minimum window sizes, so adjust if the initial window size is too small.
2019 if (window->windowed.w < data->system_limits.min_width ||
2020 window->windowed.h < data->system_limits.min_height) {
2021
2022 // Warn if the window frame will be larger than the content surface.
2023 SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO,
2024 "Window dimensions (%i, %i) are smaller than the system enforced minimum (%i, %i); window borders will be larger than the content surface.",
2025 window->windowed.w, window->windowed.h, data->system_limits.min_width, data->system_limits.min_height);
2026
2027 data->current.logical_width = SDL_max(window->windowed.w, data->system_limits.min_width);
2028 data->current.logical_height = SDL_max(window->windowed.h, data->system_limits.min_height);
2029 CommitLibdecorFrame(window);
2030 }
2031 }
2032#endif
2033 Wayland_SetWindowResizable(_this, window, !!(window->flags & SDL_WINDOW_RESIZABLE));
2034
2035 // We're finally done putting the window together, raise if possible
2036 if (c->activation_manager) {
2037 /* Note that we don't check for empty strings, as that is still
2038 * considered a valid activation token!
2039 */
2040 const char *activation_token = SDL_getenv("XDG_ACTIVATION_TOKEN");
2041 if (activation_token) {
2042 xdg_activation_v1_activate(c->activation_manager,
2043 activation_token,
2044 data->surface);
2045
2046 // Clear this variable, per the protocol's request
2047 SDL_unsetenv_unsafe("XDG_ACTIVATION_TOKEN");
2048 }
2049 }
2050
2051 data->show_hide_sync_required = true;
2052 struct wl_callback *cb = wl_display_sync(_this->internal->display);
2053 wl_callback_add_listener(cb, &show_hide_sync_listener, (void*)((uintptr_t)window->id));
2054
2055 // Send an exposure event to signal that the client should draw.
2056 if (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME) {
2057 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
2058 }
2059}
2060
2061static void Wayland_ReleasePopup(SDL_VideoDevice *_this, SDL_Window *popup)
2062{
2063 SDL_WindowData *popupdata;
2064
2065 // Basic sanity checks to weed out the weird popup closures
2066 if (!SDL_ObjectValid(popup, SDL_OBJECT_TYPE_WINDOW)) {
2067 return;
2068 }
2069 popupdata = popup->internal;
2070 if (!popupdata) {
2071 return;
2072 }
2073
2074 // This may already be freed by a parent popup!
2075 if (popupdata->shell_surface.xdg.popup.xdg_popup == NULL) {
2076 return;
2077 }
2078
2079 if (popup->flags & SDL_WINDOW_POPUP_MENU) {
2080 SDL_Window *new_focus = popup->parent;
2081 bool set_focus = popup == SDL_GetKeyboardFocus();
2082
2083 // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed.
2084 while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) {
2085 new_focus = new_focus->parent;
2086
2087 // If some window in the chain currently had focus, set it to the new lowest-level window.
2088 if (!set_focus) {
2089 set_focus = new_focus == SDL_GetKeyboardFocus();
2090 }
2091 }
2092
2093 SetKeyboardFocus(new_focus, set_focus);
2094 }
2095
2096 xdg_popup_destroy(popupdata->shell_surface.xdg.popup.xdg_popup);
2097 xdg_positioner_destroy(popupdata->shell_surface.xdg.popup.xdg_positioner);
2098 popupdata->shell_surface.xdg.popup.xdg_popup = NULL;
2099 popupdata->shell_surface.xdg.popup.xdg_positioner = NULL;
2100
2101 SDL_PropertiesID props = SDL_GetWindowProperties(popup);
2102 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POPUP_POINTER, NULL);
2103 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POSITIONER_POINTER, NULL);
2104}
2105
2106void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
2107{
2108 SDL_VideoData *data = _this->internal;
2109 SDL_WindowData *wind = window->internal;
2110 SDL_PropertiesID props = SDL_GetWindowProperties(window);
2111
2112 // Custom surfaces have nothing to destroy and are always considered to be 'shown'; nothing to do here.
2113 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) {
2114 return;
2115 }
2116
2117 /* The window was shown, but the sync point hasn't yet been reached.
2118 * Pump events to avoid a possible protocol violation.
2119 */
2120 if (wind->show_hide_sync_required) {
2121 WAYLAND_wl_display_roundtrip(data->display);
2122 }
2123
2124 wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_HIDDEN;
2125
2126 if (wind->server_decoration) {
2127 zxdg_toplevel_decoration_v1_destroy(wind->server_decoration);
2128 wind->server_decoration = NULL;
2129 }
2130
2131 // Be sure to detach after this is done, otherwise ShowWindow crashes!
2132 if (wind->shell_surface_type != WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) {
2133 wl_surface_attach(wind->surface, NULL, 0, 0);
2134 wl_surface_commit(wind->surface);
2135 }
2136
2137 // Clean up the export handle.
2138 if (wind->exported) {
2139 zxdg_exported_v2_destroy(wind->exported);
2140 wind->exported = NULL;
2141
2142 SDL_SetStringProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, NULL);
2143 }
2144
2145 if (wind->xdg_dialog_v1) {
2146 xdg_dialog_v1_destroy(wind->xdg_dialog_v1);
2147 wind->xdg_dialog_v1 = NULL;
2148 }
2149
2150#ifdef HAVE_LIBDECOR_H
2151 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
2152 if (wind->shell_surface.libdecor.frame) {
2153 libdecor_frame_unref(wind->shell_surface.libdecor.frame);
2154 wind->shell_surface.libdecor.frame = NULL;
2155
2156 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL);
2157 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL);
2158 }
2159 } else
2160#endif
2161 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) {
2162 Wayland_ReleasePopup(_this, window);
2163 } else if (wind->shell_surface.xdg.toplevel.xdg_toplevel) {
2164 xdg_toplevel_destroy(wind->shell_surface.xdg.toplevel.xdg_toplevel);
2165 wind->shell_surface.xdg.toplevel.xdg_toplevel = NULL;
2166 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL);
2167 }
2168 if (wind->shell_surface.xdg.surface) {
2169 xdg_surface_destroy(wind->shell_surface.xdg.surface);
2170 wind->shell_surface.xdg.surface = NULL;
2171 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL);
2172 }
2173
2174 wind->show_hide_sync_required = true;
2175 struct wl_callback *cb = wl_display_sync(_this->internal->display);
2176 wl_callback_add_listener(cb, &show_hide_sync_listener, (void*)((uintptr_t)window->id));
2177}
2178
2179static void handle_xdg_activation_done(void *data,
2180 struct xdg_activation_token_v1 *xdg_activation_token_v1,
2181 const char *token)
2182{
2183 SDL_WindowData *window = data;
2184 if (xdg_activation_token_v1 == window->activation_token) {
2185 xdg_activation_v1_activate(window->waylandData->activation_manager,
2186 token,
2187 window->surface);
2188 xdg_activation_token_v1_destroy(window->activation_token);
2189 window->activation_token = NULL;
2190 }
2191}
2192
2193static const struct xdg_activation_token_v1_listener activation_listener_xdg = {
2194 handle_xdg_activation_done
2195};
2196
2197/* The xdg-activation protocol considers "activation" to be one of two things:
2198 *
2199 * 1: Raising a window to the top and flashing the titlebar
2200 * 2: Flashing the titlebar while keeping the window where it is
2201 *
2202 * As you might expect from Wayland, the general policy is to go with #2 unless
2203 * the client can prove to the compositor beyond a reasonable doubt that raising
2204 * the window will not be malicuous behavior.
2205 *
2206 * For SDL this means RaiseWindow and FlashWindow both use the same protocol,
2207 * but in different ways: RaiseWindow will provide as _much_ information as
2208 * possible while FlashWindow will provide as _little_ information as possible,
2209 * to nudge the compositor into doing what we want.
2210 *
2211 * This isn't _strictly_ what the protocol says will happen, but this is what
2212 * current implementations are doing (as of writing, YMMV in the far distant
2213 * future).
2214 *
2215 * -flibit
2216 */
2217static void Wayland_activate_window(SDL_VideoData *data, SDL_WindowData *target_wind, bool set_serial)
2218{
2219 struct SDL_WaylandInput * input = data->input;
2220 SDL_Window *focus = SDL_GetKeyboardFocus();
2221 struct wl_surface *requesting_surface = focus ? focus->internal->surface : NULL;
2222
2223 if (data->activation_manager) {
2224 if (target_wind->activation_token) {
2225 // We're about to overwrite this with a new request
2226 xdg_activation_token_v1_destroy(target_wind->activation_token);
2227 }
2228
2229 target_wind->activation_token = xdg_activation_v1_get_activation_token(data->activation_manager);
2230 xdg_activation_token_v1_add_listener(target_wind->activation_token,
2231 &activation_listener_xdg,
2232 target_wind);
2233
2234 /* Note that we are not setting the app_id here.
2235 *
2236 * Hypothetically we could set the app_id from data->classname, but
2237 * that part of the API is for _external_ programs, not ourselves.
2238 *
2239 * -flibit
2240 */
2241 if (requesting_surface) {
2242 // This specifies the surface from which the activation request is originating, not the activation target surface.
2243 xdg_activation_token_v1_set_surface(target_wind->activation_token, requesting_surface);
2244 }
2245 if (set_serial && input && input->seat) {
2246 xdg_activation_token_v1_set_serial(target_wind->activation_token, input->last_implicit_grab_serial, input->seat);
2247 }
2248 xdg_activation_token_v1_commit(target_wind->activation_token);
2249 }
2250}
2251
2252void Wayland_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window)
2253{
2254 Wayland_activate_window(_this->internal, window->internal, true);
2255}
2256
2257bool Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation)
2258{
2259 /* Not setting the serial will specify 'urgency' without switching focus as per
2260 * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/9#note_854977
2261 */
2262 Wayland_activate_window(_this->internal, window->internal, false);
2263 return true;
2264}
2265
2266SDL_FullscreenResult Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window,
2267 SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)
2268{
2269 SDL_WindowData *wind = window->internal;
2270 struct wl_output *output = display->internal->output;
2271
2272 // Custom surfaces have no toplevel to make fullscreen.
2273 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) {
2274 return SDL_FULLSCREEN_FAILED;
2275 }
2276
2277 if (wind->show_hide_sync_required) {
2278 WAYLAND_wl_display_roundtrip(_this->internal->display);
2279 }
2280
2281 // Flushing old events pending a new one, ignore this request.
2282 if (wind->drop_fullscreen_requests) {
2283 return SDL_FULLSCREEN_SUCCEEDED;
2284 }
2285
2286 wind->drop_fullscreen_requests = true;
2287 FlushPendingEvents(window);
2288 wind->drop_fullscreen_requests = false;
2289
2290 // Nothing to do if the window is not fullscreen, and this isn't an explicit enter request.
2291 if (!wind->is_fullscreen) {
2292 if (fullscreen == SDL_FULLSCREEN_OP_UPDATE) {
2293 // Request was out of date; signal the video core not to update any state.
2294 return SDL_FULLSCREEN_PENDING;
2295 } else if (fullscreen == SDL_FULLSCREEN_OP_LEAVE) {
2296 // Already not fullscreen; nothing to do.
2297 return SDL_FULLSCREEN_SUCCEEDED;
2298 }
2299 }
2300
2301 // Don't send redundant fullscreen set/unset events.
2302 if (fullscreen != wind->is_fullscreen) {
2303 wind->fullscreen_was_positioned = fullscreen;
2304 SetFullscreen(window, fullscreen ? output : NULL);
2305 } else if (wind->is_fullscreen) {
2306 /*
2307 * If the window is already fullscreen, this is likely a request to switch between
2308 * fullscreen and fullscreen desktop, change outputs, or change the video mode.
2309 *
2310 * If the window is already positioned on the target output, just update the
2311 * window geometry.
2312 */
2313 if (wind->last_displayID != display->id) {
2314 wind->fullscreen_was_positioned = true;
2315 SetFullscreen(window, output);
2316 } else {
2317 ConfigureWindowGeometry(window);
2318 CommitLibdecorFrame(window);
2319
2320 return SDL_FULLSCREEN_SUCCEEDED;
2321 }
2322 }
2323
2324 return SDL_FULLSCREEN_PENDING;
2325}
2326
2327void Wayland_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window)
2328{
2329 SDL_WindowData *wind = window->internal;
2330
2331 // Not currently fullscreen or maximized, and no state pending; nothing to do.
2332 if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED)) &&
2333 !wind->fullscreen_deadline_count && !wind->maximized_restored_deadline_count) {
2334 return;
2335 }
2336
2337#ifdef HAVE_LIBDECOR_H
2338 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
2339 if (!wind->shell_surface.libdecor.frame) {
2340 return; // Can't do anything yet, wait for ShowWindow
2341 }
2342 libdecor_frame_unset_maximized(wind->shell_surface.libdecor.frame);
2343
2344 ++wind->maximized_restored_deadline_count;
2345 struct wl_callback *cb = wl_display_sync(_this->internal->display);
2346 wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id));
2347 } else
2348#endif
2349 // Note that xdg-shell does NOT provide a way to unset minimize!
2350 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
2351 if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) {
2352 return; // Can't do anything yet, wait for ShowWindow
2353 }
2354 xdg_toplevel_unset_maximized(wind->shell_surface.xdg.toplevel.xdg_toplevel);
2355
2356 ++wind->maximized_restored_deadline_count;
2357 struct wl_callback *cb = wl_display_sync(_this->internal->display);
2358 wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id));
2359 }
2360}
2361
2362void Wayland_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered)
2363{
2364 SDL_WindowData *wind = window->internal;
2365 const SDL_VideoData *viddata = (const SDL_VideoData *)_this->internal;
2366
2367#ifdef HAVE_LIBDECOR_H
2368 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
2369 if (wind->shell_surface.libdecor.frame) {
2370 libdecor_frame_set_visibility(wind->shell_surface.libdecor.frame, bordered);
2371 }
2372 } else
2373#endif
2374 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
2375 if ((viddata->decoration_manager) && (wind->server_decoration)) {
2376 const enum zxdg_toplevel_decoration_v1_mode mode = bordered ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
2377 zxdg_toplevel_decoration_v1_set_mode(wind->server_decoration, mode);
2378 }
2379 }
2380}
2381
2382void Wayland_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable)
2383{
2384#ifdef HAVE_LIBDECOR_H
2385 const SDL_WindowData *wind = window->internal;
2386
2387 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
2388 if (!wind->shell_surface.libdecor.frame) {
2389 return; // Can't do anything yet, wait for ShowWindow
2390 }
2391 if (libdecor_frame_has_capability(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE)) {
2392 if (!resizable) {
2393 libdecor_frame_unset_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE);
2394 }
2395 } else if (resizable) {
2396 libdecor_frame_set_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE);
2397 }
2398 }
2399#endif
2400
2401 /* When changing the resize capability on libdecor windows, the limits must always
2402 * be reapplied, as when libdecor changes states, it overwrites the values internally.
2403 */
2404 SetMinMaxDimensions(window);
2405 CommitLibdecorFrame(window);
2406}
2407
2408void Wayland_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
2409{
2410 SDL_VideoData *viddata = _this->internal;
2411 SDL_WindowData *wind = window->internal;
2412
2413 if (wind->show_hide_sync_required) {
2414 WAYLAND_wl_display_roundtrip(_this->internal->display);
2415 }
2416
2417 // Not fullscreen, already maximized, and no state pending; nothing to do.
2418 if (!(window->flags & SDL_WINDOW_FULLSCREEN) && (window->flags & SDL_WINDOW_MAXIMIZED) &&
2419 !wind->fullscreen_deadline_count && !wind->maximized_restored_deadline_count) {
2420 return;
2421 }
2422
2423#ifdef HAVE_LIBDECOR_H
2424 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
2425 if (!wind->shell_surface.libdecor.frame) {
2426 return; // Can't do anything yet, wait for ShowWindow
2427 }
2428
2429 // Commit to preserve any pending size data.
2430 wl_surface_commit(wind->surface);
2431 libdecor_frame_set_maximized(wind->shell_surface.libdecor.frame);
2432
2433 ++wind->maximized_restored_deadline_count;
2434 struct wl_callback *cb = wl_display_sync(viddata->display);
2435 wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id));
2436 } else
2437#endif
2438 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
2439 if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) {
2440 return; // Can't do anything yet, wait for ShowWindow
2441 }
2442
2443 // Commit to preserve any pending size data.
2444 wl_surface_commit(wind->surface);
2445 xdg_toplevel_set_maximized(wind->shell_surface.xdg.toplevel.xdg_toplevel);
2446
2447 ++wind->maximized_restored_deadline_count;
2448 struct wl_callback *cb = wl_display_sync(viddata->display);
2449 wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id));
2450 }
2451}
2452
2453void Wayland_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
2454{
2455 SDL_WindowData *wind = window->internal;
2456
2457 if (!(wind->wm_caps & WAYLAND_WM_CAPS_MINIMIZE)) {
2458 return;
2459 }
2460
2461#ifdef HAVE_LIBDECOR_H
2462 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
2463 if (!wind->shell_surface.libdecor.frame) {
2464 return; // Can't do anything yet, wait for ShowWindow
2465 }
2466 libdecor_frame_set_minimized(wind->shell_surface.libdecor.frame);
2467 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
2468 } else
2469#endif
2470 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
2471 if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) {
2472 return; // Can't do anything yet, wait for ShowWindow
2473 }
2474 xdg_toplevel_set_minimized(wind->shell_surface.xdg.toplevel.xdg_toplevel);
2475 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
2476 }
2477}
2478
2479bool Wayland_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window)
2480{
2481 SDL_VideoData *data = _this->internal;
2482
2483 /* This may look suspiciously like SetWindowGrab, despite SetMouseRect not
2484 * implicitly doing a grab. And you're right! Wayland doesn't let us mess
2485 * around with mouse focus whatsoever, so it just happens to be that the
2486 * work that we can do in these two functions ends up being the same.
2487 *
2488 * Just know that this call lets you confine with a rect, SetWindowGrab
2489 * lets you confine without a rect.
2490 */
2491 if (SDL_RectEmpty(&window->mouse_rect) && !(window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
2492 return Wayland_input_unconfine_pointer(data->input, window);
2493 } else {
2494 return Wayland_input_confine_pointer(data->input, window);
2495 }
2496}
2497
2498bool Wayland_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
2499{
2500 SDL_VideoData *data = _this->internal;
2501
2502 if (grabbed) {
2503 return Wayland_input_confine_pointer(data->input, window);
2504 } else if (SDL_RectEmpty(&window->mouse_rect)) {
2505 return Wayland_input_unconfine_pointer(data->input, window);
2506 }
2507
2508 return true;
2509}
2510
2511bool Wayland_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
2512{
2513 SDL_VideoData *data = _this->internal;
2514
2515 if (grabbed) {
2516 return Wayland_input_grab_keyboard(window, data->input);
2517 } else {
2518 return Wayland_input_ungrab_keyboard(window);
2519 }
2520}
2521
2522bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
2523{
2524 SDL_WindowData *data;
2525 SDL_VideoData *c = _this->internal;
2526 struct wl_surface *external_surface = (struct wl_surface *)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER,
2527 (struct wl_surface *)SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL));
2528 const bool custom_surface_role = (external_surface != NULL) || SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN, false);
2529 const bool create_egl_window = !!(window->flags & SDL_WINDOW_OPENGL) ||
2530 SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN, false);
2531
2532 data = SDL_calloc(1, sizeof(*data));
2533 if (!data) {
2534 return false;
2535 }
2536
2537 window->internal = data;
2538
2539 if (window->x == SDL_WINDOWPOS_UNDEFINED) {
2540 window->x = 0;
2541 }
2542 if (window->y == SDL_WINDOWPOS_UNDEFINED) {
2543 window->y = 0;
2544 }
2545
2546 data->waylandData = c;
2547 data->sdlwindow = window;
2548
2549 // Default to all capabilities
2550 data->wm_caps = WAYLAND_WM_CAPS_ALL;
2551
2552 data->scale_factor = 1.0;
2553
2554 if (SDL_WINDOW_IS_POPUP(window)) {
2555 data->scale_to_display = window->parent->internal->scale_to_display;
2556 data->scale_factor = window->parent->internal->scale_factor;
2557 EnsurePopupPositionIsValid(window, &window->x, &window->y);
2558 } else {
2559 for (int i = 0; i < _this->num_displays; i++) {
2560 data->scale_factor = SDL_max(data->scale_factor, _this->displays[i]->internal->scale_factor);
2561 }
2562 }
2563
2564 data->outputs = NULL;
2565 data->num_outputs = 0;
2566 data->scale_to_display = c->scale_to_display_enabled;
2567
2568 // Cache the app_id at creation time, as it may change before the window is mapped.
2569 data->app_id = SDL_strdup(SDL_GetAppID());
2570
2571 if (!data->scale_to_display) {
2572 data->requested.logical_width = window->floating.w;
2573 data->requested.logical_height = window->floating.h;
2574 } else {
2575 data->requested.logical_width = PixelToPoint(window, window->floating.w);
2576 data->requested.logical_height = PixelToPoint(window, window->floating.h);
2577 data->requested.pixel_width = window->floating.w;
2578 data->requested.pixel_height = window->floating.h;
2579 }
2580
2581 if (!external_surface) {
2582 data->surface = wl_compositor_create_surface(c->compositor);
2583 wl_surface_add_listener(data->surface, &surface_listener, data);
2584 wl_surface_set_user_data(data->surface, data);
2585 SDL_WAYLAND_register_surface(data->surface);
2586 } else {
2587 window->flags |= SDL_WINDOW_EXTERNAL;
2588 data->surface = external_surface;
2589
2590 /* External surfaces are registered by being put in a list, as changing tags or userdata
2591 * can cause problems with external toolkits.
2592 */
2593 Wayland_AddWindowDataToExternalList(data);
2594 }
2595
2596 /* Always attach a viewport and fractional scale manager if available and the surface is not custom/external,
2597 * or the custom/external surface was explicitly flagged as high pixel density aware, which signals that the
2598 * application wants SDL to handle scaling.
2599 */
2600 if (!custom_surface_role || (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)) {
2601 if (c->viewporter) {
2602 data->viewport = wp_viewporter_get_viewport(c->viewporter, data->surface);
2603
2604 // The viewport always uses the entire buffer.
2605 wp_viewport_set_source(data->viewport,
2606 wl_fixed_from_int(-1), wl_fixed_from_int(-1),
2607 wl_fixed_from_int(-1), wl_fixed_from_int(-1));
2608 }
2609 if (c->fractional_scale_manager) {
2610 data->fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(c->fractional_scale_manager, data->surface);
2611 wp_fractional_scale_v1_add_listener(data->fractional_scale, &fractional_scale_listener, data);
2612 }
2613 }
2614
2615 if (!custom_surface_role) {
2616 if (c->wp_color_manager_v1) {
2617 data->wp_color_management_surface_feedback = wp_color_manager_v1_get_surface_feedback(c->wp_color_manager_v1, data->surface);
2618 wp_color_management_surface_feedback_v1_add_listener(data->wp_color_management_surface_feedback, &color_management_surface_feedback_listener, data);
2619 Wayland_GetColorInfoForWindow(data, true);
2620 } else if (c->frog_color_management_factory_v1) {
2621 data->frog_color_managed_surface = frog_color_management_factory_v1_get_color_managed_surface(c->frog_color_management_factory_v1, data->surface);
2622 frog_color_managed_surface_add_listener(data->frog_color_managed_surface, &frog_surface_listener, data);
2623 }
2624
2625 if (c->wp_alpha_modifier_v1) {
2626 data->wp_alpha_modifier_surface_v1 = wp_alpha_modifier_v1_get_surface(c->wp_alpha_modifier_v1, data->surface);
2627 wp_alpha_modifier_surface_v1_set_multiplier(data->wp_alpha_modifier_surface_v1, SDL_MAX_UINT32);
2628 }
2629 }
2630
2631 // Must be called before EGL configuration to set the drawable backbuffer size.
2632 ConfigureWindowGeometry(window);
2633
2634 /* Fire a callback when the compositor wants a new frame rendered.
2635 * Right now this only matters for OpenGL; we use this callback to add a
2636 * wait timeout that avoids getting deadlocked by the compositor when the
2637 * window isn't visible.
2638 */
2639 if (window->flags & SDL_WINDOW_OPENGL) {
2640 data->gles_swap_frame_event_queue = WAYLAND_wl_display_create_queue(data->waylandData->display);
2641 data->gles_swap_frame_surface_wrapper = WAYLAND_wl_proxy_create_wrapper(data->surface);
2642 WAYLAND_wl_proxy_set_queue((struct wl_proxy *)data->gles_swap_frame_surface_wrapper, data->gles_swap_frame_event_queue);
2643 data->gles_swap_frame_callback = wl_surface_frame(data->gles_swap_frame_surface_wrapper);
2644 wl_callback_add_listener(data->gles_swap_frame_callback, &gles_swap_frame_listener, data);
2645 }
2646
2647 // No frame callback on external surfaces as it may already have one attached.
2648 if (!external_surface) {
2649 // Fire a callback when the compositor wants a new frame to set the surface damage region.
2650 data->surface_frame_callback = wl_surface_frame(data->surface);
2651 wl_callback_add_listener(data->surface_frame_callback, &surface_frame_listener, data);
2652 }
2653
2654 if (window->flags & SDL_WINDOW_TRANSPARENT) {
2655 if (_this->gl_config.alpha_size == 0) {
2656 _this->gl_config.alpha_size = 8;
2657 }
2658 }
2659
2660 if (create_egl_window) {
2661 data->egl_window = WAYLAND_wl_egl_window_create(data->surface, data->current.pixel_width, data->current.pixel_height);
2662 }
2663
2664#ifdef SDL_VIDEO_OPENGL_EGL
2665 if (window->flags & SDL_WINDOW_OPENGL) {
2666 // Create the GLES window surface
2667 data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->egl_window);
2668
2669 if (data->egl_surface == EGL_NO_SURFACE) {
2670 return false; // SDL_EGL_CreateSurface should have set error
2671 }
2672 }
2673#endif
2674
2675 if (c->relative_mouse_mode) {
2676 Wayland_input_enable_relative_pointer(c->input);
2677 }
2678
2679 // We may need to create an idle inhibitor for this new window
2680 Wayland_SuspendScreenSaver(_this);
2681
2682 if (!custom_surface_role) {
2683#ifdef HAVE_LIBDECOR_H
2684 if (c->shell.libdecor && !SDL_WINDOW_IS_POPUP(window)) {
2685 data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR;
2686 } else
2687#endif
2688 if (c->shell.xdg) {
2689 if (SDL_WINDOW_IS_POPUP(window)) {
2690 data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP;
2691 } else {
2692 data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL;
2693 }
2694 } // All other cases will be WAYLAND_SURFACE_UNKNOWN
2695 } else {
2696 // Roleless and external surfaces are always considered to be in the shown state by the backend.
2697 data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_CUSTOM;
2698 data->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_SHOWN;
2699 }
2700
2701 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, false)) {
2702 data->double_buffer = true;
2703 }
2704
2705 SDL_PropertiesID props = SDL_GetWindowProperties(window);
2706 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, data->waylandData->display);
2707 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, data->surface);
2708 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_VIEWPORT_POINTER, data->viewport);
2709 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER, data->egl_window);
2710
2711 data->hit_test_result = SDL_HITTEST_NORMAL;
2712
2713 return true;
2714}
2715
2716void Wayland_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window)
2717{
2718 // Will be committed when Wayland_SetWindowSize() is called by the video core.
2719 SetMinMaxDimensions(window);
2720}
2721
2722void Wayland_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window)
2723{
2724 // Will be committed when Wayland_SetWindowSize() is called by the video core.
2725 SetMinMaxDimensions(window);
2726}
2727
2728bool Wayland_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window)
2729{
2730 SDL_WindowData *wind = window->internal;
2731
2732 // Only popup windows can be positioned relative to the parent.
2733 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) {
2734 if (wind->shell_surface.xdg.popup.xdg_popup &&
2735 xdg_popup_get_version(wind->shell_surface.xdg.popup.xdg_popup) < XDG_POPUP_REPOSITION_SINCE_VERSION) {
2736 return SDL_Unsupported();
2737 }
2738
2739 RepositionPopup(window, false);
2740 return true;
2741 } else if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR || wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
2742 /* Catch up on any pending state before attempting to change the fullscreen window
2743 * display via a set fullscreen call to make sure the window doesn't have a pending
2744 * leave fullscreen event that it might override.
2745 */
2746 FlushPendingEvents(window);
2747
2748 if (wind->is_fullscreen) {
2749 SDL_VideoDisplay *display = SDL_GetVideoDisplayForFullscreenWindow(window);
2750 if (display && wind->last_displayID != display->id) {
2751 struct wl_output *output = display->internal->output;
2752 SetFullscreen(window, output);
2753
2754 return true;
2755 }
2756 }
2757 }
2758 return SDL_SetError("wayland cannot position non-popup windows");
2759}
2760
2761void Wayland_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
2762{
2763 SDL_WindowData *wind = window->internal;
2764
2765 /* Flush any pending state operations, as fullscreen windows do not get
2766 * explicitly resized, not strictly obeying the size of a maximized window
2767 * is a protocol violation, and pending restore events might result in a
2768 * configure event overwriting the requested size.
2769 *
2770 * Calling this on a custom surface is informative, so the size must
2771 * always be passed through.
2772 */
2773 FlushPendingEvents(window);
2774
2775 // Maximized and fullscreen windows don't get resized.
2776 if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED)) ||
2777 wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) {
2778 if (!wind->scale_to_display) {
2779 wind->requested.logical_width = window->pending.w;
2780 wind->requested.logical_height = window->pending.h;
2781 } else {
2782 wind->requested.logical_width = PixelToPoint(window, window->pending.w);
2783 wind->requested.logical_height = PixelToPoint(window, window->pending.h);
2784 wind->requested.pixel_width = window->pending.w;
2785 wind->requested.pixel_height = window->pending.h;
2786 }
2787
2788 ConfigureWindowGeometry(window);
2789 } else {
2790 // Can't resize the window.
2791 window->last_size_pending = false;
2792 }
2793
2794 // Always commit, as this may be in response to a min/max limit change.
2795 CommitLibdecorFrame(window);
2796}
2797
2798void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h)
2799{
2800 SDL_WindowData *data = window->internal;
2801
2802 *w = data->current.pixel_width;
2803 *h = data->current.pixel_height;
2804}
2805
2806float Wayland_GetWindowContentScale(SDL_VideoDevice *_this, SDL_Window *window)
2807{
2808 SDL_WindowData *wind = window->internal;
2809
2810 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY || wind->scale_to_display || wind->fullscreen_exclusive) {
2811 return (float)wind->scale_factor;
2812 }
2813
2814 return 1.0f;
2815}
2816
2817SDL_DisplayID Wayland_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window)
2818{
2819 SDL_WindowData *wind = window->internal;
2820
2821 if (wind) {
2822 return wind->last_displayID;
2823 }
2824
2825 return 0;
2826}
2827
2828bool Wayland_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity)
2829{
2830 SDL_WindowData *wind = window->internal;
2831
2832 if (wind->wp_alpha_modifier_surface_v1) {
2833 SetSurfaceOpaqueRegion(wind, !(window->flags & SDL_WINDOW_TRANSPARENT) && opacity == 1.0f);
2834 wp_alpha_modifier_surface_v1_set_multiplier(wind->wp_alpha_modifier_surface_v1, (Uint32)((double)SDL_MAX_UINT32 * (double)opacity));
2835
2836 return true;
2837 }
2838
2839 return SDL_SetError("wayland: set window opacity failed; compositor lacks support for the required wp_alpha_modifier_v1 protocol");
2840}
2841
2842void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
2843{
2844 SDL_WindowData *wind = window->internal;
2845 const char *title = window->title ? window->title : "";
2846
2847#ifdef HAVE_LIBDECOR_H
2848 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) {
2849 libdecor_frame_set_title(wind->shell_surface.libdecor.frame, title);
2850 } else
2851#endif
2852 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && wind->shell_surface.xdg.toplevel.xdg_toplevel) {
2853 xdg_toplevel_set_title(wind->shell_surface.xdg.toplevel.xdg_toplevel, title);
2854 }
2855}
2856
2857bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon)
2858{
2859 SDL_WindowData *wind = window->internal;
2860 struct xdg_toplevel *toplevel = NULL;
2861
2862 if (!_this->internal->xdg_toplevel_icon_manager_v1) {
2863 return SDL_SetError("wayland: cannot set icon; required xdg_toplevel_icon_v1 protocol not supported");
2864 }
2865
2866 if (icon->w != icon->h) {
2867 return SDL_SetError("wayland: icon width and height must be equal, got %ix%i", icon->w, icon->h);
2868 }
2869
2870 int image_count = 0;
2871 SDL_Surface **images = SDL_GetSurfaceImages(icon, &image_count);
2872 if (!images || !image_count) {
2873 return false;
2874 }
2875
2876 // Release the old icon resources.
2877 if (wind->xdg_toplevel_icon_v1) {
2878 xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1);
2879 wind->xdg_toplevel_icon_v1 = NULL;
2880 }
2881
2882 for (int i = 0; i < wind->icon_buffer_count; ++i) {
2883 Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]);
2884 }
2885 SDL_free(wind->icon_buffers);
2886 wind->icon_buffer_count = 0;
2887
2888 wind->xdg_toplevel_icon_v1 = xdg_toplevel_icon_manager_v1_create_icon(_this->internal->xdg_toplevel_icon_manager_v1);
2889 wind->icon_buffers = SDL_calloc(image_count, sizeof(struct Wayland_SHMBuffer));
2890 if (!wind->icon_buffers) {
2891 goto failure_cleanup;
2892 }
2893
2894 for (int i = 0; i < image_count; ++i) {
2895 if (images[i]->w == images[i]->h) {
2896 struct Wayland_SHMBuffer *buffer = &wind->icon_buffers[wind->icon_buffer_count];
2897
2898 if (!Wayland_AllocSHMBuffer(images[i]->w, images[i]->h, buffer)) {
2899 SDL_SetError("wayland: failed to allocate SHM buffer for the icon");
2900 goto failure_cleanup;
2901 }
2902
2903 SDL_PremultiplyAlpha(images[i]->w, images[i]->h, images[i]->format, images[i]->pixels, images[i]->pitch, SDL_PIXELFORMAT_ARGB8888, buffer->shm_data, images[i]->w * 4, true);
2904 const int scale = (int)SDL_ceil((double)images[i]->w / (double)icon->w);
2905 xdg_toplevel_icon_v1_add_buffer(wind->xdg_toplevel_icon_v1, buffer->wl_buffer, scale);
2906 wind->icon_buffer_count++;
2907 } else {
2908 SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "wayland: icon width and height must be equal, got %ix%i for image level %i; skipping", images[i]->w, images[i]->h, i);
2909 }
2910 }
2911
2912 SDL_free(images);
2913
2914#ifdef HAVE_LIBDECOR_H
2915 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) {
2916 toplevel = libdecor_frame_get_xdg_toplevel(wind->shell_surface.libdecor.frame);
2917 } else
2918#endif
2919 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && wind->shell_surface.xdg.toplevel.xdg_toplevel) {
2920 toplevel = wind->shell_surface.xdg.toplevel.xdg_toplevel;
2921 }
2922
2923 if (toplevel) {
2924 xdg_toplevel_icon_manager_v1_set_icon(_this->internal->xdg_toplevel_icon_manager_v1, toplevel, wind->xdg_toplevel_icon_v1);
2925 }
2926
2927 return true;
2928
2929failure_cleanup:
2930
2931 if (wind->xdg_toplevel_icon_v1) {
2932 xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1);
2933 wind->xdg_toplevel_icon_v1 = NULL;
2934 }
2935
2936 for (int i = 0; i < wind->icon_buffer_count; ++i) {
2937 Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]);
2938 }
2939 SDL_free(wind->icon_buffers);
2940 wind->icon_buffers = NULL;
2941 wind->icon_buffer_count = 0;
2942
2943 return false;
2944}
2945
2946void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size)
2947{
2948 SDL_WindowData *wind = window->internal;
2949 void *ret = NULL;
2950
2951 if (wind->icc_size > 0) {
2952 void *icc_map = mmap(NULL, wind->icc_size, PROT_READ, MAP_PRIVATE, wind->icc_fd, 0);
2953 if (icc_map != MAP_FAILED) {
2954 ret = SDL_malloc(wind->icc_size);
2955 if (ret) {
2956 *size = wind->icc_size;
2957 SDL_memcpy(ret, icc_map, *size);
2958 }
2959 munmap(icc_map, wind->icc_size);
2960 }
2961 }
2962
2963 return ret;
2964}
2965
2966bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window)
2967{
2968 SDL_WindowData *wind = window->internal;
2969
2970 do {
2971 WAYLAND_wl_display_roundtrip(_this->internal->display);
2972 } while (wind->fullscreen_deadline_count || wind->maximized_restored_deadline_count);
2973
2974 return true;
2975}
2976
2977void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y)
2978{
2979 SDL_WindowData *wind = window->internal;
2980
2981 if (wind->scale_to_display) {
2982 x = PixelToPoint(window, x);
2983 y = PixelToPoint(window, y);
2984 }
2985
2986#ifdef HAVE_LIBDECOR_H
2987 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
2988 if (wind->shell_surface.libdecor.frame) {
2989 libdecor_frame_show_window_menu(wind->shell_surface.libdecor.frame, wind->waylandData->input->seat, wind->waylandData->input->last_implicit_grab_serial, x, y);
2990 }
2991 } else
2992#endif
2993 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
2994 if (wind->shell_surface.xdg.toplevel.xdg_toplevel) {
2995 xdg_toplevel_show_window_menu(wind->shell_surface.xdg.toplevel.xdg_toplevel, wind->waylandData->input->seat, wind->waylandData->input->last_implicit_grab_serial, x, y);
2996 }
2997 }
2998}
2999
3000bool Wayland_SuspendScreenSaver(SDL_VideoDevice *_this)
3001{
3002 SDL_VideoData *data = _this->internal;
3003
3004#ifdef SDL_USE_LIBDBUS
3005 if (SDL_DBus_ScreensaverInhibit(_this->suspend_screensaver)) {
3006 return true;
3007 }
3008#endif
3009
3010 /* The idle_inhibit_unstable_v1 protocol suspends the screensaver
3011 on a per wl_surface basis, but SDL assumes that suspending
3012 the screensaver can be done independently of any window.
3013
3014 To reconcile these differences, we propagate the idle inhibit
3015 state to each window. If there is no window active, we will
3016 be able to inhibit idle once the first window is created.
3017 */
3018 if (data->idle_inhibit_manager) {
3019 SDL_Window *window = _this->windows;
3020 while (window) {
3021 SDL_WindowData *win_data = window->internal;
3022
3023 if (_this->suspend_screensaver && !win_data->idle_inhibitor) {
3024 win_data->idle_inhibitor =
3025 zwp_idle_inhibit_manager_v1_create_inhibitor(data->idle_inhibit_manager,
3026 win_data->surface);
3027 } else if (!_this->suspend_screensaver && win_data->idle_inhibitor) {
3028 zwp_idle_inhibitor_v1_destroy(win_data->idle_inhibitor);
3029 win_data->idle_inhibitor = NULL;
3030 }
3031
3032 window = window->next;
3033 }
3034 }
3035
3036 return true;
3037}
3038
3039void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
3040{
3041 SDL_VideoData *data = _this->internal;
3042 SDL_WindowData *wind = window->internal;
3043
3044 if (data && wind) {
3045 /* Roundtrip before destroying the window to make sure that it has received input leave events, so that
3046 * no internal structures are left pointing to the destroyed window.
3047 */
3048 if (wind->show_hide_sync_required) {
3049 WAYLAND_wl_display_roundtrip(data->display);
3050 }
3051
3052#ifdef SDL_VIDEO_OPENGL_EGL
3053 if (wind->egl_surface) {
3054 SDL_EGL_DestroySurface(_this, wind->egl_surface);
3055 }
3056#endif
3057 if (wind->egl_window) {
3058 WAYLAND_wl_egl_window_destroy(wind->egl_window);
3059 }
3060
3061 if (wind->idle_inhibitor) {
3062 zwp_idle_inhibitor_v1_destroy(wind->idle_inhibitor);
3063 }
3064
3065 if (wind->activation_token) {
3066 xdg_activation_token_v1_destroy(wind->activation_token);
3067 }
3068
3069 if (wind->viewport) {
3070 wp_viewport_destroy(wind->viewport);
3071 }
3072
3073 if (wind->fractional_scale) {
3074 wp_fractional_scale_v1_destroy(wind->fractional_scale);
3075 }
3076
3077 if (wind->wp_alpha_modifier_surface_v1) {
3078 wp_alpha_modifier_surface_v1_destroy(wind->wp_alpha_modifier_surface_v1);
3079 }
3080
3081 if (wind->frog_color_managed_surface) {
3082 frog_color_managed_surface_destroy(wind->frog_color_managed_surface);
3083 }
3084
3085 if (wind->wp_color_management_surface_feedback) {
3086 Wayland_FreeColorInfoState(wind->color_info_state);
3087 wp_color_management_surface_feedback_v1_destroy(wind->wp_color_management_surface_feedback);
3088 }
3089
3090 SDL_free(wind->outputs);
3091 SDL_free(wind->app_id);
3092
3093 if (wind->gles_swap_frame_callback) {
3094 wl_callback_destroy(wind->gles_swap_frame_callback);
3095 WAYLAND_wl_proxy_wrapper_destroy(wind->gles_swap_frame_surface_wrapper);
3096 WAYLAND_wl_event_queue_destroy(wind->gles_swap_frame_event_queue);
3097 }
3098
3099 if (wind->surface_frame_callback) {
3100 wl_callback_destroy(wind->surface_frame_callback);
3101 }
3102
3103 if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
3104 wl_surface_destroy(wind->surface);
3105 } else {
3106 Wayland_RemoveWindowDataFromExternalList(wind);
3107 }
3108
3109 if (wind->xdg_toplevel_icon_v1) {
3110 xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1);
3111 }
3112
3113 for (int i = 0; i < wind->icon_buffer_count; ++i) {
3114 Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]);
3115 }
3116 SDL_free(wind->icon_buffers);
3117 wind->icon_buffer_count = 0;
3118
3119 SDL_free(wind);
3120 WAYLAND_wl_display_flush(data->display);
3121 }
3122 window->internal = NULL;
3123}
3124
3125#endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.h b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.h
new file mode 100644
index 0000000..619fd79
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.h
@@ -0,0 +1,248 @@
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#ifndef SDL_waylandwindow_h_
25#define SDL_waylandwindow_h_
26
27#include "../SDL_sysvideo.h"
28#include "../../events/SDL_touch_c.h"
29
30#include "SDL_waylandvideo.h"
31#include "SDL_waylandshmbuffer.h"
32
33struct SDL_WaylandInput;
34
35struct SDL_WindowData
36{
37 SDL_Window *sdlwindow;
38 SDL_VideoData *waylandData;
39 struct wl_surface *surface;
40 struct wl_callback *gles_swap_frame_callback;
41 struct wl_event_queue *gles_swap_frame_event_queue;
42 struct wl_surface *gles_swap_frame_surface_wrapper;
43 struct wl_callback *surface_frame_callback;
44
45 union
46 {
47#ifdef HAVE_LIBDECOR_H
48 struct
49 {
50 struct libdecor_frame *frame;
51 bool initial_configure_seen;
52 } libdecor;
53#endif
54 struct
55 {
56 struct xdg_surface *surface;
57 union
58 {
59 struct
60 {
61 struct xdg_toplevel *xdg_toplevel;
62 } toplevel;
63 struct
64 {
65 struct xdg_popup *xdg_popup;
66 struct xdg_positioner *xdg_positioner;
67 } popup;
68 };
69 bool initial_configure_seen;
70 } xdg;
71 } shell_surface;
72 enum
73 {
74 WAYLAND_SHELL_SURFACE_TYPE_UNKNOWN = 0,
75 WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL,
76 WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP,
77 WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR,
78 WAYLAND_SHELL_SURFACE_TYPE_CUSTOM
79 } shell_surface_type;
80 enum
81 {
82 WAYLAND_SHELL_SURFACE_STATUS_HIDDEN = 0,
83 WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE,
84 WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME,
85 WAYLAND_SHELL_SURFACE_STATUS_SHOW_PENDING,
86 WAYLAND_SHELL_SURFACE_STATUS_SHOWN
87 } shell_surface_status;
88 enum
89 {
90 WAYLAND_WM_CAPS_WINDOW_MENU = 0x01,
91 WAYLAND_WM_CAPS_MAXIMIZE = 0x02,
92 WAYLAND_WM_CAPS_FULLSCREEN = 0x04,
93 WAYLAND_WM_CAPS_MINIMIZE = 0x08,
94
95 WAYLAND_WM_CAPS_ALL = WAYLAND_WM_CAPS_WINDOW_MENU |
96 WAYLAND_WM_CAPS_MAXIMIZE |
97 WAYLAND_WM_CAPS_FULLSCREEN |
98 WAYLAND_WM_CAPS_MINIMIZE
99 } wm_caps;
100
101 struct wl_egl_window *egl_window;
102 struct SDL_WaylandInput *keyboard_device;
103#ifdef SDL_VIDEO_OPENGL_EGL
104 EGLSurface egl_surface;
105#endif
106 struct zwp_locked_pointer_v1 *locked_pointer;
107 struct zwp_confined_pointer_v1 *confined_pointer;
108 struct zxdg_toplevel_decoration_v1 *server_decoration;
109 struct zwp_keyboard_shortcuts_inhibitor_v1 *key_inhibitor;
110 struct zwp_idle_inhibitor_v1 *idle_inhibitor;
111 struct xdg_activation_token_v1 *activation_token;
112 struct wp_viewport *viewport;
113 struct wp_fractional_scale_v1 *fractional_scale;
114 struct zxdg_exported_v2 *exported;
115 struct xdg_dialog_v1 *xdg_dialog_v1;
116 struct wp_alpha_modifier_surface_v1 *wp_alpha_modifier_surface_v1;
117 struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1;
118 struct frog_color_managed_surface *frog_color_managed_surface;
119 struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback;
120
121 struct Wayland_ColorInfoState *color_info_state;
122
123 SDL_AtomicInt swap_interval_ready;
124
125 SDL_DisplayData **outputs;
126 int num_outputs;
127
128 SDL_Window *keyboard_focus;
129
130 char *app_id;
131 double scale_factor;
132
133 struct Wayland_SHMBuffer *icon_buffers;
134 int icon_buffer_count;
135
136 struct
137 {
138 double x;
139 double y;
140 } pointer_scale;
141
142 // The in-flight window size request.
143 struct
144 {
145 // The requested logical window size.
146 int logical_width;
147 int logical_height;
148
149 // The size of the window in pixels, when using screen space scaling.
150 int pixel_width;
151 int pixel_height;
152 } requested;
153
154 // The current size of the window and drawable backing store.
155 struct
156 {
157 // The size of the underlying window.
158 int logical_width;
159 int logical_height;
160
161 // The size of the window backbuffer in pixels.
162 int pixel_width;
163 int pixel_height;
164 } current;
165
166 // The last compositor requested parameters; used for deduplication of window geometry configuration.
167 struct
168 {
169 int width;
170 int height;
171 } last_configure;
172
173 // System enforced window size limits.
174 struct
175 {
176 // Minimum allowed logical window size.
177 int min_width;
178 int min_height;
179 } system_limits;
180
181 struct
182 {
183 int width;
184 int height;
185 } toplevel_bounds;
186
187 SDL_DisplayID last_displayID;
188 int fullscreen_deadline_count;
189 int maximized_restored_deadline_count;
190 Uint64 last_focus_event_time_ns;
191 int icc_fd;
192 Uint32 icc_size;
193 bool floating;
194 bool suspended;
195 bool resizing;
196 bool active;
197 bool drop_interactive_resizes;
198 bool is_fullscreen;
199 bool fullscreen_exclusive;
200 bool drop_fullscreen_requests;
201 bool fullscreen_was_positioned;
202 bool show_hide_sync_required;
203 bool scale_to_display;
204 bool reparenting_required;
205 bool double_buffer;
206
207 SDL_HitTestResult hit_test_result;
208
209 struct wl_list external_window_list_link;
210};
211
212extern void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window);
213extern void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window);
214extern void Wayland_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window);
215extern SDL_FullscreenResult Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_FullscreenOp fullscreen);
216extern void Wayland_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window);
217extern void Wayland_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window);
218extern bool Wayland_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window);
219extern bool Wayland_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed);
220extern bool Wayland_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed);
221extern void Wayland_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window);
222extern void Wayland_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered);
223extern void Wayland_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable);
224extern bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props);
225extern bool Wayland_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window);
226extern void Wayland_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window);
227extern void Wayland_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window);
228extern void Wayland_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window);
229extern void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h);
230extern SDL_DisplayID Wayland_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window);
231extern bool Wayland_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent_window);
232extern bool Wayland_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal);
233extern bool Wayland_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity);
234extern void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window);
235extern void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y);
236extern void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
237extern bool Wayland_SuspendScreenSaver(SDL_VideoDevice *_this);
238extern bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon);
239extern float Wayland_GetWindowContentScale(SDL_VideoDevice *_this, SDL_Window *window);
240extern void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size);
241
242extern bool Wayland_SetWindowHitTest(SDL_Window *window, bool enabled);
243extern bool Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);
244extern bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window);
245
246extern void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, SDL_DisplayData *display_data);
247
248#endif // SDL_waylandwindow_h_