summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/emscripten
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/emscripten')
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenevents.c1117
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenevents.h31
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenframebuffer.c161
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenframebuffer.h30
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenmouse.c216
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenmouse.h34
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenopengles.c162
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenopengles.h43
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.c459
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.h54
10 files changed, 2307 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenevents.c b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenevents.c
new file mode 100644
index 0000000..49a140f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenevents.c
@@ -0,0 +1,1117 @@
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_EMSCRIPTEN
25
26#include <emscripten/html5.h>
27#include <emscripten/dom_pk_codes.h>
28
29#include "../../events/SDL_dropevents_c.h"
30#include "../../events/SDL_events_c.h"
31#include "../../events/SDL_keyboard_c.h"
32#include "../../events/SDL_touch_c.h"
33
34#include "SDL_emscriptenevents.h"
35#include "SDL_emscriptenvideo.h"
36
37/*
38Emscripten PK code to scancode
39https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
40https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
41*/
42static const SDL_Scancode emscripten_scancode_table[] = {
43 /* 0x00 "Unidentified" */ SDL_SCANCODE_UNKNOWN,
44 /* 0x01 "Escape" */ SDL_SCANCODE_ESCAPE,
45 /* 0x02 "Digit0" */ SDL_SCANCODE_0,
46 /* 0x03 "Digit1" */ SDL_SCANCODE_1,
47 /* 0x04 "Digit2" */ SDL_SCANCODE_2,
48 /* 0x05 "Digit3" */ SDL_SCANCODE_3,
49 /* 0x06 "Digit4" */ SDL_SCANCODE_4,
50 /* 0x07 "Digit5" */ SDL_SCANCODE_5,
51 /* 0x08 "Digit6" */ SDL_SCANCODE_6,
52 /* 0x09 "Digit7" */ SDL_SCANCODE_7,
53 /* 0x0A "Digit8" */ SDL_SCANCODE_8,
54 /* 0x0B "Digit9" */ SDL_SCANCODE_9,
55 /* 0x0C "Minus" */ SDL_SCANCODE_MINUS,
56 /* 0x0D "Equal" */ SDL_SCANCODE_EQUALS,
57 /* 0x0E "Backspace" */ SDL_SCANCODE_BACKSPACE,
58 /* 0x0F "Tab" */ SDL_SCANCODE_TAB,
59 /* 0x10 "KeyQ" */ SDL_SCANCODE_Q,
60 /* 0x11 "KeyW" */ SDL_SCANCODE_W,
61 /* 0x12 "KeyE" */ SDL_SCANCODE_E,
62 /* 0x13 "KeyR" */ SDL_SCANCODE_R,
63 /* 0x14 "KeyT" */ SDL_SCANCODE_T,
64 /* 0x15 "KeyY" */ SDL_SCANCODE_Y,
65 /* 0x16 "KeyU" */ SDL_SCANCODE_U,
66 /* 0x17 "KeyI" */ SDL_SCANCODE_I,
67 /* 0x18 "KeyO" */ SDL_SCANCODE_O,
68 /* 0x19 "KeyP" */ SDL_SCANCODE_P,
69 /* 0x1A "BracketLeft" */ SDL_SCANCODE_LEFTBRACKET,
70 /* 0x1B "BracketRight" */ SDL_SCANCODE_RIGHTBRACKET,
71 /* 0x1C "Enter" */ SDL_SCANCODE_RETURN,
72 /* 0x1D "ControlLeft" */ SDL_SCANCODE_LCTRL,
73 /* 0x1E "KeyA" */ SDL_SCANCODE_A,
74 /* 0x1F "KeyS" */ SDL_SCANCODE_S,
75 /* 0x20 "KeyD" */ SDL_SCANCODE_D,
76 /* 0x21 "KeyF" */ SDL_SCANCODE_F,
77 /* 0x22 "KeyG" */ SDL_SCANCODE_G,
78 /* 0x23 "KeyH" */ SDL_SCANCODE_H,
79 /* 0x24 "KeyJ" */ SDL_SCANCODE_J,
80 /* 0x25 "KeyK" */ SDL_SCANCODE_K,
81 /* 0x26 "KeyL" */ SDL_SCANCODE_L,
82 /* 0x27 "Semicolon" */ SDL_SCANCODE_SEMICOLON,
83 /* 0x28 "Quote" */ SDL_SCANCODE_APOSTROPHE,
84 /* 0x29 "Backquote" */ SDL_SCANCODE_GRAVE,
85 /* 0x2A "ShiftLeft" */ SDL_SCANCODE_LSHIFT,
86 /* 0x2B "Backslash" */ SDL_SCANCODE_BACKSLASH,
87 /* 0x2C "KeyZ" */ SDL_SCANCODE_Z,
88 /* 0x2D "KeyX" */ SDL_SCANCODE_X,
89 /* 0x2E "KeyC" */ SDL_SCANCODE_C,
90 /* 0x2F "KeyV" */ SDL_SCANCODE_V,
91 /* 0x30 "KeyB" */ SDL_SCANCODE_B,
92 /* 0x31 "KeyN" */ SDL_SCANCODE_N,
93 /* 0x32 "KeyM" */ SDL_SCANCODE_M,
94 /* 0x33 "Comma" */ SDL_SCANCODE_COMMA,
95 /* 0x34 "Period" */ SDL_SCANCODE_PERIOD,
96 /* 0x35 "Slash" */ SDL_SCANCODE_SLASH,
97 /* 0x36 "ShiftRight" */ SDL_SCANCODE_RSHIFT,
98 /* 0x37 "NumpadMultiply" */ SDL_SCANCODE_KP_MULTIPLY,
99 /* 0x38 "AltLeft" */ SDL_SCANCODE_LALT,
100 /* 0x39 "Space" */ SDL_SCANCODE_SPACE,
101 /* 0x3A "CapsLock" */ SDL_SCANCODE_CAPSLOCK,
102 /* 0x3B "F1" */ SDL_SCANCODE_F1,
103 /* 0x3C "F2" */ SDL_SCANCODE_F2,
104 /* 0x3D "F3" */ SDL_SCANCODE_F3,
105 /* 0x3E "F4" */ SDL_SCANCODE_F4,
106 /* 0x3F "F5" */ SDL_SCANCODE_F5,
107 /* 0x40 "F6" */ SDL_SCANCODE_F6,
108 /* 0x41 "F7" */ SDL_SCANCODE_F7,
109 /* 0x42 "F8" */ SDL_SCANCODE_F8,
110 /* 0x43 "F9" */ SDL_SCANCODE_F9,
111 /* 0x44 "F10" */ SDL_SCANCODE_F10,
112 /* 0x45 "Pause" */ SDL_SCANCODE_PAUSE,
113 /* 0x46 "ScrollLock" */ SDL_SCANCODE_SCROLLLOCK,
114 /* 0x47 "Numpad7" */ SDL_SCANCODE_KP_7,
115 /* 0x48 "Numpad8" */ SDL_SCANCODE_KP_8,
116 /* 0x49 "Numpad9" */ SDL_SCANCODE_KP_9,
117 /* 0x4A "NumpadSubtract" */ SDL_SCANCODE_KP_MINUS,
118 /* 0x4B "Numpad4" */ SDL_SCANCODE_KP_4,
119 /* 0x4C "Numpad5" */ SDL_SCANCODE_KP_5,
120 /* 0x4D "Numpad6" */ SDL_SCANCODE_KP_6,
121 /* 0x4E "NumpadAdd" */ SDL_SCANCODE_KP_PLUS,
122 /* 0x4F "Numpad1" */ SDL_SCANCODE_KP_1,
123 /* 0x50 "Numpad2" */ SDL_SCANCODE_KP_2,
124 /* 0x51 "Numpad3" */ SDL_SCANCODE_KP_3,
125 /* 0x52 "Numpad0" */ SDL_SCANCODE_KP_0,
126 /* 0x53 "NumpadDecimal" */ SDL_SCANCODE_KP_PERIOD,
127 /* 0x54 "PrintScreen" */ SDL_SCANCODE_PRINTSCREEN,
128 /* 0x55 */ SDL_SCANCODE_UNKNOWN,
129 /* 0x56 "IntlBackslash" */ SDL_SCANCODE_NONUSBACKSLASH,
130 /* 0x57 "F11" */ SDL_SCANCODE_F11,
131 /* 0x58 "F12" */ SDL_SCANCODE_F12,
132 /* 0x59 "NumpadEqual" */ SDL_SCANCODE_KP_EQUALS,
133 /* 0x5A */ SDL_SCANCODE_UNKNOWN,
134 /* 0x5B */ SDL_SCANCODE_UNKNOWN,
135 /* 0x5C */ SDL_SCANCODE_UNKNOWN,
136 /* 0x5D */ SDL_SCANCODE_UNKNOWN,
137 /* 0x5E */ SDL_SCANCODE_UNKNOWN,
138 /* 0x5F */ SDL_SCANCODE_UNKNOWN,
139 /* 0x60 */ SDL_SCANCODE_UNKNOWN,
140 /* 0x61 */ SDL_SCANCODE_UNKNOWN,
141 /* 0x62 */ SDL_SCANCODE_UNKNOWN,
142 /* 0x63 */ SDL_SCANCODE_UNKNOWN,
143 /* 0x64 "F13" */ SDL_SCANCODE_F13,
144 /* 0x65 "F14" */ SDL_SCANCODE_F14,
145 /* 0x66 "F15" */ SDL_SCANCODE_F15,
146 /* 0x67 "F16" */ SDL_SCANCODE_F16,
147 /* 0x68 "F17" */ SDL_SCANCODE_F17,
148 /* 0x69 "F18" */ SDL_SCANCODE_F18,
149 /* 0x6A "F19" */ SDL_SCANCODE_F19,
150 /* 0x6B "F20" */ SDL_SCANCODE_F20,
151 /* 0x6C "F21" */ SDL_SCANCODE_F21,
152 /* 0x6D "F22" */ SDL_SCANCODE_F22,
153 /* 0x6E "F23" */ SDL_SCANCODE_F23,
154 /* 0x6F */ SDL_SCANCODE_UNKNOWN,
155 /* 0x70 "KanaMode" */ SDL_SCANCODE_INTERNATIONAL2,
156 /* 0x71 "Lang2" */ SDL_SCANCODE_LANG2,
157 /* 0x72 "Lang1" */ SDL_SCANCODE_LANG1,
158 /* 0x73 "IntlRo" */ SDL_SCANCODE_INTERNATIONAL1,
159 /* 0x74 */ SDL_SCANCODE_UNKNOWN,
160 /* 0x75 */ SDL_SCANCODE_UNKNOWN,
161 /* 0x76 "F24" */ SDL_SCANCODE_F24,
162 /* 0x77 */ SDL_SCANCODE_UNKNOWN,
163 /* 0x78 */ SDL_SCANCODE_UNKNOWN,
164 /* 0x79 "Convert" */ SDL_SCANCODE_INTERNATIONAL4,
165 /* 0x7A */ SDL_SCANCODE_UNKNOWN,
166 /* 0x7B "NonConvert" */ SDL_SCANCODE_INTERNATIONAL5,
167 /* 0x7C */ SDL_SCANCODE_UNKNOWN,
168 /* 0x7D "IntlYen" */ SDL_SCANCODE_INTERNATIONAL3,
169 /* 0x7E "NumpadComma" */ SDL_SCANCODE_KP_COMMA
170};
171
172static SDL_Scancode Emscripten_MapScanCode(const char *code)
173{
174 const DOM_PK_CODE_TYPE pk_code = emscripten_compute_dom_pk_code(code);
175 if (pk_code < SDL_arraysize(emscripten_scancode_table)) {
176 return emscripten_scancode_table[pk_code];
177 }
178
179 switch (pk_code) {
180 case DOM_PK_PASTE:
181 return SDL_SCANCODE_PASTE;
182 case DOM_PK_MEDIA_TRACK_PREVIOUS:
183 return SDL_SCANCODE_MEDIA_PREVIOUS_TRACK;
184 case DOM_PK_CUT:
185 return SDL_SCANCODE_CUT;
186 case DOM_PK_COPY:
187 return SDL_SCANCODE_COPY;
188 case DOM_PK_MEDIA_TRACK_NEXT:
189 return SDL_SCANCODE_MEDIA_NEXT_TRACK;
190 case DOM_PK_NUMPAD_ENTER:
191 return SDL_SCANCODE_KP_ENTER;
192 case DOM_PK_CONTROL_RIGHT:
193 return SDL_SCANCODE_RCTRL;
194 case DOM_PK_AUDIO_VOLUME_MUTE:
195 return SDL_SCANCODE_MUTE;
196 case DOM_PK_MEDIA_PLAY_PAUSE:
197 return SDL_SCANCODE_MEDIA_PLAY_PAUSE;
198 case DOM_PK_MEDIA_STOP:
199 return SDL_SCANCODE_MEDIA_STOP;
200 case DOM_PK_EJECT:
201 return SDL_SCANCODE_MEDIA_EJECT;
202 case DOM_PK_AUDIO_VOLUME_DOWN:
203 return SDL_SCANCODE_VOLUMEDOWN;
204 case DOM_PK_AUDIO_VOLUME_UP:
205 return SDL_SCANCODE_VOLUMEUP;
206 case DOM_PK_BROWSER_HOME:
207 return SDL_SCANCODE_AC_HOME;
208 case DOM_PK_NUMPAD_DIVIDE:
209 return SDL_SCANCODE_KP_DIVIDE;
210 case DOM_PK_ALT_RIGHT:
211 return SDL_SCANCODE_RALT;
212 case DOM_PK_HELP:
213 return SDL_SCANCODE_HELP;
214 case DOM_PK_NUM_LOCK:
215 return SDL_SCANCODE_NUMLOCKCLEAR;
216 case DOM_PK_HOME:
217 return SDL_SCANCODE_HOME;
218 case DOM_PK_ARROW_UP:
219 return SDL_SCANCODE_UP;
220 case DOM_PK_PAGE_UP:
221 return SDL_SCANCODE_PAGEUP;
222 case DOM_PK_ARROW_LEFT:
223 return SDL_SCANCODE_LEFT;
224 case DOM_PK_ARROW_RIGHT:
225 return SDL_SCANCODE_RIGHT;
226 case DOM_PK_END:
227 return SDL_SCANCODE_END;
228 case DOM_PK_ARROW_DOWN:
229 return SDL_SCANCODE_DOWN;
230 case DOM_PK_PAGE_DOWN:
231 return SDL_SCANCODE_PAGEDOWN;
232 case DOM_PK_INSERT:
233 return SDL_SCANCODE_INSERT;
234 case DOM_PK_DELETE:
235 return SDL_SCANCODE_DELETE;
236 case DOM_PK_META_LEFT:
237 return SDL_SCANCODE_LGUI;
238 case DOM_PK_META_RIGHT:
239 return SDL_SCANCODE_RGUI;
240 case DOM_PK_CONTEXT_MENU:
241 return SDL_SCANCODE_APPLICATION;
242 case DOM_PK_POWER:
243 return SDL_SCANCODE_POWER;
244 case DOM_PK_BROWSER_SEARCH:
245 return SDL_SCANCODE_AC_SEARCH;
246 case DOM_PK_BROWSER_FAVORITES:
247 return SDL_SCANCODE_AC_BOOKMARKS;
248 case DOM_PK_BROWSER_REFRESH:
249 return SDL_SCANCODE_AC_REFRESH;
250 case DOM_PK_BROWSER_STOP:
251 return SDL_SCANCODE_AC_STOP;
252 case DOM_PK_BROWSER_FORWARD:
253 return SDL_SCANCODE_AC_FORWARD;
254 case DOM_PK_BROWSER_BACK:
255 return SDL_SCANCODE_AC_BACK;
256 case DOM_PK_MEDIA_SELECT:
257 return SDL_SCANCODE_MEDIA_SELECT;
258 }
259
260 return SDL_SCANCODE_UNKNOWN;
261}
262
263static EM_BOOL Emscripten_HandlePointerLockChange(int eventType, const EmscriptenPointerlockChangeEvent *changeEvent, void *userData)
264{
265 SDL_WindowData *window_data = (SDL_WindowData *)userData;
266 // keep track of lock losses, so we can regrab if/when appropriate.
267 window_data->has_pointer_lock = changeEvent->isActive;
268 return 0;
269}
270
271static EM_BOOL Emscripten_HandleMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
272{
273 SDL_WindowData *window_data = userData;
274 const bool isPointerLocked = window_data->has_pointer_lock;
275 float mx, my;
276
277 // rescale (in case canvas is being scaled)
278 double client_w, client_h, xscale, yscale;
279 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h);
280 xscale = window_data->window->w / client_w;
281 yscale = window_data->window->h / client_h;
282
283 if (isPointerLocked) {
284 mx = (float)(mouseEvent->movementX * xscale);
285 my = (float)(mouseEvent->movementY * yscale);
286 } else {
287 mx = (float)(mouseEvent->targetX * xscale);
288 my = (float)(mouseEvent->targetY * yscale);
289 }
290
291 SDL_SendMouseMotion(0, window_data->window, SDL_DEFAULT_MOUSE_ID, isPointerLocked, mx, my);
292 return 0;
293}
294
295static EM_BOOL Emscripten_HandleMouseButton(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
296{
297 SDL_WindowData *window_data = userData;
298 Uint8 sdl_button;
299 bool sdl_button_state;
300 double css_w, css_h;
301 bool prevent_default = false; // needed for iframe implementation in Chrome-based browsers.
302
303 switch (mouseEvent->button) {
304 case 0:
305 sdl_button = SDL_BUTTON_LEFT;
306 break;
307 case 1:
308 sdl_button = SDL_BUTTON_MIDDLE;
309 break;
310 case 2:
311 sdl_button = SDL_BUTTON_RIGHT;
312 break;
313 default:
314 return 0;
315 }
316
317 const SDL_Mouse *mouse = SDL_GetMouse();
318 SDL_assert(mouse != NULL);
319
320 if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) {
321 if (mouse->relative_mode && !window_data->has_pointer_lock) {
322 emscripten_request_pointerlock(window_data->canvas_id, 0); // try to regrab lost pointer lock.
323 }
324 sdl_button_state = true;
325 } else {
326 sdl_button_state = false;
327 prevent_default = SDL_EventEnabled(SDL_EVENT_MOUSE_BUTTON_UP);
328 }
329
330 SDL_SendMouseButton(0, window_data->window, SDL_DEFAULT_MOUSE_ID, sdl_button, sdl_button_state);
331
332 // We have an imaginary mouse capture, because we need SDL to not drop our imaginary mouse focus when we leave the canvas.
333 if (mouse->auto_capture) {
334 if (SDL_GetMouseState(NULL, NULL) != 0) {
335 window_data->window->flags |= SDL_WINDOW_MOUSE_CAPTURE;
336 } else {
337 window_data->window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
338 }
339 }
340
341 if ((eventType == EMSCRIPTEN_EVENT_MOUSEUP) && window_data->mouse_focus_loss_pending) {
342 window_data->mouse_focus_loss_pending = (window_data->window->flags & SDL_WINDOW_MOUSE_CAPTURE) != 0;
343 if (!window_data->mouse_focus_loss_pending) {
344 SDL_SetMouseFocus(NULL);
345 }
346 } else {
347 // Do not consume the event if the mouse is outside of the canvas.
348 emscripten_get_element_css_size(window_data->canvas_id, &css_w, &css_h);
349 if (mouseEvent->targetX < 0 || mouseEvent->targetX >= css_w ||
350 mouseEvent->targetY < 0 || mouseEvent->targetY >= css_h) {
351 return 0;
352 }
353 }
354
355 return prevent_default;
356}
357
358static EM_BOOL Emscripten_HandleMouseFocus(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
359{
360 SDL_WindowData *window_data = userData;
361
362 const bool isPointerLocked = window_data->has_pointer_lock;
363
364 if (!isPointerLocked) {
365 // rescale (in case canvas is being scaled)
366 float mx, my;
367 double client_w, client_h;
368 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h);
369
370 mx = (float)(mouseEvent->targetX * (window_data->window->w / client_w));
371 my = (float)(mouseEvent->targetY * (window_data->window->h / client_h));
372 SDL_SendMouseMotion(0, window_data->window, SDL_GLOBAL_MOUSE_ID, isPointerLocked, mx, my);
373 }
374
375 const bool isenter = (eventType == EMSCRIPTEN_EVENT_MOUSEENTER);
376 if (isenter && window_data->mouse_focus_loss_pending) {
377 window_data->mouse_focus_loss_pending = false; // just drop the state, but don't send the enter event.
378 } else if (!isenter && (window_data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
379 window_data->mouse_focus_loss_pending = true; // waiting on a mouse button to let go before we send the mouse focus update.
380 } else {
381 SDL_SetMouseFocus(isenter ? window_data->window : NULL);
382 }
383
384 return SDL_EventEnabled(SDL_EVENT_MOUSE_MOTION); // !!! FIXME: should this be MOUSE_MOTION or something else?
385}
386
387static EM_BOOL Emscripten_HandleWheel(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData)
388{
389 SDL_WindowData *window_data = userData;
390
391 float deltaY = wheelEvent->deltaY;
392 float deltaX = wheelEvent->deltaX;
393
394 switch (wheelEvent->deltaMode) {
395 case DOM_DELTA_PIXEL:
396 deltaX /= 100; // 100 pixels make up a step
397 deltaY /= 100; // 100 pixels make up a step
398 break;
399 case DOM_DELTA_LINE:
400 deltaX /= 3; // 3 lines make up a step
401 deltaY /= 3; // 3 lines make up a step
402 break;
403 case DOM_DELTA_PAGE:
404 deltaX *= 80; // A page makes up 80 steps
405 deltaY *= 80; // A page makes up 80 steps
406 break;
407 }
408
409 SDL_SendMouseWheel(0, window_data->window, SDL_DEFAULT_MOUSE_ID, deltaX, -deltaY, SDL_MOUSEWHEEL_NORMAL);
410 return SDL_EventEnabled(SDL_EVENT_MOUSE_WHEEL);
411}
412
413static EM_BOOL Emscripten_HandleFocus(int eventType, const EmscriptenFocusEvent *wheelEvent, void *userData)
414{
415 SDL_WindowData *window_data = userData;
416 SDL_EventType sdl_event_type;
417
418 /* If the user switches away while keys are pressed (such as
419 * via Alt+Tab), key release events won't be received. */
420 if (eventType == EMSCRIPTEN_EVENT_BLUR) {
421 SDL_ResetKeyboard();
422 }
423
424 sdl_event_type = (eventType == EMSCRIPTEN_EVENT_FOCUS) ? SDL_EVENT_WINDOW_FOCUS_GAINED : SDL_EVENT_WINDOW_FOCUS_LOST;
425 SDL_SetKeyboardFocus(sdl_event_type == SDL_EVENT_WINDOW_FOCUS_GAINED ? window_data->window : NULL);
426 return SDL_EventEnabled(sdl_event_type);
427}
428
429static EM_BOOL Emscripten_HandleTouch(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData)
430{
431 SDL_WindowData *window_data = (SDL_WindowData *)userData;
432 int i;
433 double client_w, client_h;
434 int preventDefault = 0;
435
436 const SDL_TouchID deviceId = 1;
437 if (SDL_AddTouch(deviceId, SDL_TOUCH_DEVICE_DIRECT, "") < 0) {
438 return 0;
439 }
440
441 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h);
442
443 for (i = 0; i < touchEvent->numTouches; i++) {
444 SDL_FingerID id;
445 float x, y;
446
447 if (!touchEvent->touches[i].isChanged) {
448 continue;
449 }
450
451 id = touchEvent->touches[i].identifier + 1;
452 if (client_w <= 1) {
453 x = 0.5f;
454 } else {
455 x = touchEvent->touches[i].targetX / (client_w - 1);
456 }
457 if (client_h <= 1) {
458 y = 0.5f;
459 } else {
460 y = touchEvent->touches[i].targetY / (client_h - 1);
461 }
462
463 if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) {
464 SDL_SendTouch(0, deviceId, id, window_data->window, SDL_EVENT_FINGER_DOWN, x, y, 1.0f);
465
466 // disable browser scrolling/pinch-to-zoom if app handles touch events
467 if (!preventDefault && SDL_EventEnabled(SDL_EVENT_FINGER_DOWN)) {
468 preventDefault = 1;
469 }
470 } else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) {
471 SDL_SendTouchMotion(0, deviceId, id, window_data->window, x, y, 1.0f);
472 } else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) {
473 SDL_SendTouch(0, deviceId, id, window_data->window, SDL_EVENT_FINGER_UP, x, y, 1.0f);
474
475 // block browser's simulated mousedown/mouseup on touchscreen devices
476 preventDefault = 1;
477 } else if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) {
478 SDL_SendTouch(0, deviceId, id, window_data->window, SDL_EVENT_FINGER_CANCELED, x, y, 1.0f);
479 }
480 }
481
482 return preventDefault;
483}
484
485static bool IsFunctionKey(SDL_Scancode scancode)
486{
487 if (scancode >= SDL_SCANCODE_F1 && scancode <= SDL_SCANCODE_F12) {
488 return true;
489 }
490 if (scancode >= SDL_SCANCODE_F13 && scancode <= SDL_SCANCODE_F24) {
491 return true;
492 }
493 return false;
494}
495
496/* This is a great tool to see web keyboard events live:
497 * https://w3c.github.io/uievents/tools/key-event-viewer.html
498 */
499static EM_BOOL Emscripten_HandleKey(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData)
500{
501 SDL_WindowData *window_data = (SDL_WindowData *)userData;
502 SDL_Scancode scancode = Emscripten_MapScanCode(keyEvent->code);
503 SDL_Keycode keycode = SDLK_UNKNOWN;
504 bool prevent_default = false;
505 bool is_nav_key = false;
506
507 if (scancode == SDL_SCANCODE_UNKNOWN) {
508 if (SDL_strcmp(keyEvent->key, "Sleep") == 0) {
509 scancode = SDL_SCANCODE_SLEEP;
510 } else if (SDL_strcmp(keyEvent->key, "ChannelUp") == 0) {
511 scancode = SDL_SCANCODE_CHANNEL_INCREMENT;
512 } else if (SDL_strcmp(keyEvent->key, "ChannelDown") == 0) {
513 scancode = SDL_SCANCODE_CHANNEL_DECREMENT;
514 } else if (SDL_strcmp(keyEvent->key, "MediaPlay") == 0) {
515 scancode = SDL_SCANCODE_MEDIA_PLAY;
516 } else if (SDL_strcmp(keyEvent->key, "MediaPause") == 0) {
517 scancode = SDL_SCANCODE_MEDIA_PAUSE;
518 } else if (SDL_strcmp(keyEvent->key, "MediaRecord") == 0) {
519 scancode = SDL_SCANCODE_MEDIA_RECORD;
520 } else if (SDL_strcmp(keyEvent->key, "MediaFastForward") == 0) {
521 scancode = SDL_SCANCODE_MEDIA_FAST_FORWARD;
522 } else if (SDL_strcmp(keyEvent->key, "MediaRewind") == 0) {
523 scancode = SDL_SCANCODE_MEDIA_REWIND;
524 } else if (SDL_strcmp(keyEvent->key, "Close") == 0) {
525 scancode = SDL_SCANCODE_AC_CLOSE;
526 } else if (SDL_strcmp(keyEvent->key, "New") == 0) {
527 scancode = SDL_SCANCODE_AC_NEW;
528 } else if (SDL_strcmp(keyEvent->key, "Open") == 0) {
529 scancode = SDL_SCANCODE_AC_OPEN;
530 } else if (SDL_strcmp(keyEvent->key, "Print") == 0) {
531 scancode = SDL_SCANCODE_AC_PRINT;
532 } else if (SDL_strcmp(keyEvent->key, "Save") == 0) {
533 scancode = SDL_SCANCODE_AC_SAVE;
534 } else if (SDL_strcmp(keyEvent->key, "Props") == 0) {
535 scancode = SDL_SCANCODE_AC_PROPERTIES;
536 }
537 }
538
539 if (scancode == SDL_SCANCODE_UNKNOWN) {
540 // KaiOS Left Soft Key and Right Soft Key, they act as OK/Next/Menu and Cancel/Back/Clear
541 if (SDL_strcmp(keyEvent->key, "SoftLeft") == 0) {
542 scancode = SDL_SCANCODE_AC_FORWARD;
543 } else if (SDL_strcmp(keyEvent->key, "SoftRight") == 0) {
544 scancode = SDL_SCANCODE_AC_BACK;
545 }
546 }
547
548 if (keyEvent->location == 0 && SDL_utf8strlen(keyEvent->key) == 1) {
549 const char *key = keyEvent->key;
550 keycode = SDL_StepUTF8(&key, NULL);
551 if (keycode == SDL_INVALID_UNICODE_CODEPOINT) {
552 keycode = SDLK_UNKNOWN;
553 }
554 }
555
556 if (keycode != SDLK_UNKNOWN) {
557 prevent_default = SDL_SendKeyboardKeyAndKeycode(0, SDL_DEFAULT_KEYBOARD_ID, 0, scancode, keycode, (eventType == EMSCRIPTEN_EVENT_KEYDOWN));
558 } else {
559 prevent_default = SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, 0, scancode, (eventType == EMSCRIPTEN_EVENT_KEYDOWN));
560 }
561
562 /* if TEXTINPUT events are enabled we can't prevent keydown or we won't get keypress
563 * we need to ALWAYS prevent backspace and tab otherwise chrome takes action and does bad navigation UX
564 */
565 if ((scancode == SDL_SCANCODE_BACKSPACE) ||
566 (scancode == SDL_SCANCODE_TAB) ||
567 (scancode == SDL_SCANCODE_LEFT) ||
568 (scancode == SDL_SCANCODE_UP) ||
569 (scancode == SDL_SCANCODE_RIGHT) ||
570 (scancode == SDL_SCANCODE_DOWN) ||
571 IsFunctionKey(scancode) ||
572 keyEvent->ctrlKey) {
573 is_nav_key = true;
574 }
575
576 if ((eventType == EMSCRIPTEN_EVENT_KEYDOWN) && SDL_TextInputActive(window_data->window) && !is_nav_key) {
577 prevent_default = false;
578 }
579
580 return prevent_default;
581}
582
583static EM_BOOL Emscripten_HandleKeyPress(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData)
584{
585 SDL_WindowData *window_data = (SDL_WindowData *)userData;
586
587 if (SDL_TextInputActive(window_data->window)) {
588 char text[5];
589 char *end = SDL_UCS4ToUTF8(keyEvent->charCode, text);
590 *end = '\0';
591 SDL_SendKeyboardText(text);
592 return EM_TRUE;
593 }
594 return EM_FALSE;
595}
596
597static EM_BOOL Emscripten_HandleFullscreenChange(int eventType, const EmscriptenFullscreenChangeEvent *fullscreenChangeEvent, void *userData)
598{
599 SDL_WindowData *window_data = userData;
600
601 if (fullscreenChangeEvent->isFullscreen) {
602 SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0);
603 window_data->fullscreen_mode_flags = 0;
604 } else {
605 SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0);
606 }
607
608 SDL_UpdateFullscreenMode(window_data->window, fullscreenChangeEvent->isFullscreen, false);
609
610 return 0;
611}
612
613static EM_BOOL Emscripten_HandleResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
614{
615 SDL_WindowData *window_data = userData;
616 bool force = false;
617
618 // update pixel ratio
619 if (window_data->window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
620 if (window_data->pixel_ratio != emscripten_get_device_pixel_ratio()) {
621 window_data->pixel_ratio = emscripten_get_device_pixel_ratio();
622 force = true;
623 }
624 }
625
626 if (!(window_data->window->flags & SDL_WINDOW_FULLSCREEN)) {
627 // this will only work if the canvas size is set through css
628 if (window_data->window->flags & SDL_WINDOW_RESIZABLE) {
629 double w = window_data->window->w;
630 double h = window_data->window->h;
631
632 if (window_data->external_size) {
633 emscripten_get_element_css_size(window_data->canvas_id, &w, &h);
634 }
635
636 emscripten_set_canvas_element_size(window_data->canvas_id, SDL_lroundf(w * window_data->pixel_ratio), SDL_lroundf(h * window_data->pixel_ratio));
637
638 // set_canvas_size unsets this
639 if (!window_data->external_size && window_data->pixel_ratio != 1.0f) {
640 emscripten_set_element_css_size(window_data->canvas_id, w, h);
641 }
642
643 if (force) {
644 // force the event to trigger, so pixel ratio changes can be handled
645 window_data->window->w = 0;
646 window_data->window->h = 0;
647 }
648
649 SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(w), SDL_lroundf(h));
650 }
651 }
652
653 return 0;
654}
655
656EM_BOOL
657Emscripten_HandleCanvasResize(int eventType, const void *reserved, void *userData)
658{
659 // this is used during fullscreen changes
660 SDL_WindowData *window_data = userData;
661
662 if (window_data->fullscreen_resize) {
663 double css_w, css_h;
664 emscripten_get_element_css_size(window_data->canvas_id, &css_w, &css_h);
665 SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(css_w), SDL_lroundf(css_h));
666 }
667
668 return 0;
669}
670
671static EM_BOOL Emscripten_HandleVisibilityChange(int eventType, const EmscriptenVisibilityChangeEvent *visEvent, void *userData)
672{
673 SDL_WindowData *window_data = userData;
674 SDL_SendWindowEvent(window_data->window, visEvent->hidden ? SDL_EVENT_WINDOW_HIDDEN : SDL_EVENT_WINDOW_SHOWN, 0, 0);
675 return 0;
676}
677
678static const char *Emscripten_HandleBeforeUnload(int eventType, const void *reserved, void *userData)
679{
680 /* This event will need to be handled synchronously, e.g. using
681 SDL_AddEventWatch, as the page is being closed *now*. */
682 // No need to send a SDL_EVENT_QUIT, the app won't get control again.
683 SDL_SendAppEvent(SDL_EVENT_TERMINATING);
684 return ""; // don't trigger confirmation dialog
685}
686
687static EM_BOOL Emscripten_HandleOrientationChange(int eventType, const EmscriptenOrientationChangeEvent *orientationChangeEvent, void *userData)
688{
689 SDL_DisplayOrientation orientation;
690 switch (orientationChangeEvent->orientationIndex) {
691 #define CHECK_ORIENTATION(emsdk, sdl) case EMSCRIPTEN_ORIENTATION_##emsdk: orientation = SDL_ORIENTATION_##sdl; break
692 CHECK_ORIENTATION(LANDSCAPE_PRIMARY, LANDSCAPE);
693 CHECK_ORIENTATION(LANDSCAPE_SECONDARY, LANDSCAPE_FLIPPED);
694 CHECK_ORIENTATION(PORTRAIT_PRIMARY, PORTRAIT);
695 CHECK_ORIENTATION(PORTRAIT_SECONDARY, PORTRAIT_FLIPPED);
696 #undef CHECK_ORIENTATION
697 default: orientation = SDL_ORIENTATION_UNKNOWN; break;
698 }
699
700 SDL_WindowData *window_data = (SDL_WindowData *) userData;
701 SDL_SendDisplayEvent(SDL_GetVideoDisplayForWindow(window_data->window), SDL_EVENT_DISPLAY_ORIENTATION, orientation, 0);
702
703 return 0;
704}
705
706// IF YOU CHANGE THIS STRUCTURE, YOU NEED TO UPDATE THE JAVASCRIPT THAT FILLS IT IN: makePointerEventCStruct, below.
707typedef struct Emscripten_PointerEvent
708{
709 int pointerid;
710 int button;
711 int buttons;
712 float movementX;
713 float movementY;
714 float targetX;
715 float targetY;
716 float pressure;
717 float tangential_pressure;
718 float tiltx;
719 float tilty;
720 float rotation;
721} Emscripten_PointerEvent;
722
723static void Emscripten_UpdatePointerFromEvent(SDL_WindowData *window_data, const Emscripten_PointerEvent *event)
724{
725 const SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) event->pointerid);
726 if (pen) {
727 // rescale (in case canvas is being scaled)
728 double client_w, client_h;
729 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h);
730 const double xscale = window_data->window->w / client_w;
731 const double yscale = window_data->window->h / client_h;
732
733 const bool isPointerLocked = window_data->has_pointer_lock;
734 float mx, my;
735 if (isPointerLocked) {
736 mx = (float)(event->movementX * xscale);
737 my = (float)(event->movementY * yscale);
738 } else {
739 mx = (float)(event->targetX * xscale);
740 my = (float)(event->targetY * yscale);
741 }
742
743 SDL_SendPenMotion(0, pen, window_data->window, mx, my);
744
745 if (event->button == 0) { // pen touch
746 bool down = ((event->buttons & 1) != 0);
747 SDL_SendPenTouch(0, pen, window_data->window, false, down);
748 } else if (event->button == 5) { // eraser touch...? Not sure if this is right...
749 bool down = ((event->buttons & 32) != 0);
750 SDL_SendPenTouch(0, pen, window_data->window, true, down);
751 } else if (event->button == 1) {
752 bool down = ((event->buttons & 4) != 0);
753 SDL_SendPenButton(0, pen, window_data->window, 2, down);
754 } else if (event->button == 2) {
755 bool down = ((event->buttons & 2) != 0);
756 SDL_SendPenButton(0, pen, window_data->window, 1, down);
757 }
758
759 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_PRESSURE, event->pressure);
760 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_TANGENTIAL_PRESSURE, event->tangential_pressure);
761 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_XTILT, event->tiltx);
762 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_YTILT, event->tilty);
763 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_ROTATION, event->rotation);
764 }
765}
766
767EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerEnter(SDL_WindowData *window_data, const Emscripten_PointerEvent *event)
768{
769 // Web browsers offer almost none of this information as specifics, but can without warning offer any of these specific things.
770 SDL_PenInfo peninfo;
771 SDL_zero(peninfo);
772 peninfo.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_ROTATION | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT | SDL_PEN_CAPABILITY_TANGENTIAL_PRESSURE | SDL_PEN_CAPABILITY_ERASER;
773 peninfo.max_tilt = 90.0f;
774 peninfo.num_buttons = 2;
775 peninfo.subtype = SDL_PEN_TYPE_PEN;
776 SDL_AddPenDevice(0, NULL, &peninfo, (void *) (size_t) event->pointerid);
777 Emscripten_UpdatePointerFromEvent(window_data, event);
778}
779
780EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerLeave(SDL_WindowData *window_data, const Emscripten_PointerEvent *event)
781{
782 const SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) event->pointerid);
783 if (pen) {
784 Emscripten_UpdatePointerFromEvent(window_data, event); // last data updates?
785 SDL_RemovePenDevice(0, pen);
786 }
787}
788
789EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerGeneric(SDL_WindowData *window_data, const Emscripten_PointerEvent *event)
790{
791 Emscripten_UpdatePointerFromEvent(window_data, event);
792}
793
794static void Emscripten_set_pointer_event_callbacks(SDL_WindowData *data)
795{
796 MAIN_THREAD_EM_ASM({
797 var target = document.querySelector(UTF8ToString($1));
798 if (target) {
799 var data = $0;
800
801 if (typeof(Module['SDL3']) === 'undefined') {
802 Module['SDL3'] = {};
803 }
804 var SDL3 = Module['SDL3'];
805
806 var makePointerEventCStruct = function(event) {
807 var ptr = 0;
808 if (event.pointerType == "pen") {
809 ptr = _SDL_malloc($2);
810 if (ptr != 0) {
811 var rect = target.getBoundingClientRect();
812 var idx = ptr >> 2;
813 HEAP32[idx++] = event.pointerId;
814 HEAP32[idx++] = (typeof(event.button) !== "undefined") ? event.button : -1;
815 HEAP32[idx++] = event.buttons;
816 HEAPF32[idx++] = event.movementX;
817 HEAPF32[idx++] = event.movementY;
818 HEAPF32[idx++] = event.clientX - rect.left;
819 HEAPF32[idx++] = event.clientY - rect.top;
820 HEAPF32[idx++] = event.pressure;
821 HEAPF32[idx++] = event.tangentialPressure;
822 HEAPF32[idx++] = event.tiltX;
823 HEAPF32[idx++] = event.tiltY;
824 HEAPF32[idx++] = event.twist;
825 }
826 }
827 return ptr;
828 };
829
830 SDL3.eventHandlerPointerEnter = function(event) {
831 var d = makePointerEventCStruct(event); if (d != 0) { _Emscripten_HandlePointerEnter(data, d); _SDL_free(d); }
832 };
833 target.addEventListener("pointerenter", SDL3.eventHandlerPointerEnter);
834
835 SDL3.eventHandlerPointerLeave = function(event) {
836 var d = makePointerEventCStruct(event); if (d != 0) { _Emscripten_HandlePointerLeave(data, d); _SDL_free(d); }
837 };
838 target.addEventListener("pointerleave", SDL3.eventHandlerPointerLeave);
839 target.addEventListener("pointercancel", SDL3.eventHandlerPointerLeave); // catch this, just in case.
840
841 SDL3.eventHandlerPointerGeneric = function(event) {
842 var d = makePointerEventCStruct(event); if (d != 0) { _Emscripten_HandlePointerGeneric(data, d); _SDL_free(d); }
843 };
844 target.addEventListener("pointerdown", SDL3.eventHandlerPointerGeneric);
845 target.addEventListener("pointerup", SDL3.eventHandlerPointerGeneric);
846 target.addEventListener("pointermove", SDL3.eventHandlerPointerGeneric);
847 }
848 }, data, data->canvas_id, sizeof (Emscripten_PointerEvent));
849}
850
851static void Emscripten_unset_pointer_event_callbacks(SDL_WindowData *data)
852{
853 MAIN_THREAD_EM_ASM({
854 var target = document.querySelector(UTF8ToString($0));
855 if (target) {
856 var SDL3 = Module['SDL3'];
857 target.removeEventListener("pointerenter", SDL3.eventHandlerPointerEnter);
858 target.removeEventListener("pointerleave", SDL3.eventHandlerPointerLeave);
859 target.removeEventListener("pointercancel", SDL3.eventHandlerPointerLeave);
860 target.removeEventListener("pointerdown", SDL3.eventHandlerPointerGeneric);
861 target.removeEventListener("pointerup", SDL3.eventHandlerPointerGeneric);
862 target.removeEventListener("pointermove", SDL3.eventHandlerPointerGeneric);
863 SDL3.eventHandlerPointerEnter = undefined;
864 SDL3.eventHandlerPointerLeave = undefined;
865 SDL3.eventHandlerPointerGeneric = undefined;
866 }
867 }, data->canvas_id);
868}
869
870// IF YOU CHANGE THIS STRUCTURE, YOU NEED TO UPDATE THE JAVASCRIPT THAT FILLS IT IN: makeDropEventCStruct, below.
871typedef struct Emscripten_DropEvent
872{
873 int x;
874 int y;
875} Emscripten_DropEvent;
876
877EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragEvent(SDL_WindowData *window_data, const Emscripten_DropEvent *event)
878{
879 SDL_SendDropPosition(window_data->window, event->x, event->y);
880}
881
882EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragCompleteEvent(SDL_WindowData *window_data)
883{
884 SDL_SendDropComplete(window_data->window);
885}
886
887EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragTextEvent(SDL_WindowData *window_data, char *text)
888{
889 SDL_SendDropText(window_data->window, text);
890}
891
892EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragFileEvent(SDL_WindowData *window_data, char *filename)
893{
894 SDL_SendDropFile(window_data->window, NULL, filename);
895}
896
897EM_JS_DEPS(dragndrop, "$writeArrayToMemory");
898
899static void Emscripten_set_drag_event_callbacks(SDL_WindowData *data)
900{
901 MAIN_THREAD_EM_ASM({
902 var target = document.querySelector(UTF8ToString($1));
903 if (target) {
904 var data = $0;
905
906 if (typeof(Module['SDL3']) === 'undefined') {
907 Module['SDL3'] = {};
908 }
909 var SDL3 = Module['SDL3'];
910
911 var makeDropEventCStruct = function(event) {
912 var ptr = 0;
913 ptr = _SDL_malloc($2);
914 if (ptr != 0) {
915 var idx = ptr >> 2;
916 var rect = target.getBoundingClientRect();
917 HEAP32[idx++] = event.clientX - rect.left;
918 HEAP32[idx++] = event.clientY - rect.top;
919 }
920 return ptr;
921 };
922
923 SDL3.eventHandlerDropDragover = function(event) {
924 event.preventDefault();
925 var d = makeDropEventCStruct(event); if (d != 0) { _Emscripten_SendDragEvent(data, d); _SDL_free(d); }
926 };
927 target.addEventListener("dragover", SDL3.eventHandlerDropDragover);
928
929 SDL3.drop_count = 0;
930 FS.mkdir("/tmp/filedrop");
931 SDL3.eventHandlerDropDrop = function(event) {
932 event.preventDefault();
933 if (event.dataTransfer.types.includes("text/plain")) {
934 let plain_text = stringToNewUTF8(event.dataTransfer.getData("text/plain"));
935 _Emscripten_SendDragTextEvent(data, plain_text);
936 _free(plain_text);
937 } else if (event.dataTransfer.types.includes("Files")) {
938 for (let i = 0; i < event.dataTransfer.files.length; i++) {
939 const file = event.dataTransfer.files.item(i);
940 const file_reader = new FileReader();
941 file_reader.readAsArrayBuffer(file);
942 file_reader.onload = function(event) {
943 const fs_dropdir = `/tmp/filedrop/${SDL3.drop_count}`;
944 SDL3.drop_count += 1;
945
946 const fs_filepath = `${fs_dropdir}/${file.name}`;
947 const c_fs_filepath = stringToNewUTF8(fs_filepath);
948 const contents_array8 = new Uint8Array(event.target.result);
949
950 FS.mkdir(fs_dropdir);
951 var stream = FS.open(fs_filepath, "w");
952 FS.write(stream, contents_array8, 0, contents_array8.length, 0);
953 FS.close(stream);
954
955 _Emscripten_SendDragFileEvent(data, c_fs_filepath);
956 _free(c_fs_filepath);
957 _Emscripten_SendDragCompleteEvent(data);
958 };
959 }
960 }
961 _Emscripten_SendDragCompleteEvent(data);
962 };
963 target.addEventListener("drop", SDL3.eventHandlerDropDrop);
964
965 SDL3.eventHandlerDropDragend = function(event) {
966 event.preventDefault();
967 _Emscripten_SendDragCompleteEvent(data);
968 };
969 target.addEventListener("dragend", SDL3.eventHandlerDropDragend);
970 target.addEventListener("dragleave", SDL3.eventHandlerDropDragend);
971 }
972 }, data, data->canvas_id, sizeof (Emscripten_DropEvent));
973}
974
975static void Emscripten_unset_drag_event_callbacks(SDL_WindowData *data)
976{
977 MAIN_THREAD_EM_ASM({
978 var target = document.querySelector(UTF8ToString($0));
979 if (target) {
980 var SDL3 = Module['SDL3'];
981 target.removeEventListener("dragleave", SDL3.eventHandlerDropDragend);
982 target.removeEventListener("dragend", SDL3.eventHandlerDropDragend);
983 target.removeEventListener("drop", SDL3.eventHandlerDropDrop);
984 SDL3.drop_count = undefined;
985
986 function recursive_remove(dirpath) {
987 FS.readdir(dirpath).forEach((filename) => {
988 const p = `${dirpath}/${filename}`;
989 const p_s = FS.stat(p);
990 if (FS.isFile(p_s.mode)) {
991 FS.unlink(p);
992 } else if (FS.isDir(p)) {
993 recursive_remove(p);
994 }
995 });
996 FS.rmdir(dirpath);
997 }("/tmp/filedrop");
998
999 FS.rmdir("/tmp/filedrop");
1000 target.removeEventListener("dragover", SDL3.eventHandlerDropDragover);
1001 SDL3.eventHandlerDropDragover = undefined;
1002 SDL3.eventHandlerDropDrop = undefined;
1003 SDL3.eventHandlerDropDragend = undefined;
1004 }
1005 }, data->canvas_id);
1006}
1007
1008void Emscripten_RegisterEventHandlers(SDL_WindowData *data)
1009{
1010 const char *keyElement;
1011
1012 // There is only one window and that window is the canvas
1013 emscripten_set_mousemove_callback(data->canvas_id, data, 0, Emscripten_HandleMouseMove);
1014
1015 emscripten_set_mousedown_callback(data->canvas_id, data, 0, Emscripten_HandleMouseButton);
1016 emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, data, 0, Emscripten_HandleMouseButton);
1017
1018 emscripten_set_mouseenter_callback(data->canvas_id, data, 0, Emscripten_HandleMouseFocus);
1019 emscripten_set_mouseleave_callback(data->canvas_id, data, 0, Emscripten_HandleMouseFocus);
1020
1021 emscripten_set_wheel_callback(data->canvas_id, data, 0, Emscripten_HandleWheel);
1022
1023 emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, data, 0, Emscripten_HandleFocus);
1024 emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, data, 0, Emscripten_HandleFocus);
1025
1026 emscripten_set_orientationchange_callback(data, 0, Emscripten_HandleOrientationChange);
1027
1028 emscripten_set_touchstart_callback(data->canvas_id, data, 0, Emscripten_HandleTouch);
1029 emscripten_set_touchend_callback(data->canvas_id, data, 0, Emscripten_HandleTouch);
1030 emscripten_set_touchmove_callback(data->canvas_id, data, 0, Emscripten_HandleTouch);
1031 emscripten_set_touchcancel_callback(data->canvas_id, data, 0, Emscripten_HandleTouch);
1032
1033 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, data, 0, Emscripten_HandlePointerLockChange);
1034
1035 // Keyboard events are awkward
1036 keyElement = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT);
1037 if (!keyElement || !*keyElement) {
1038 keyElement = EMSCRIPTEN_EVENT_TARGET_WINDOW;
1039 }
1040
1041 if (SDL_strcmp(keyElement, "#none") != 0) {
1042 emscripten_set_keydown_callback(keyElement, data, 0, Emscripten_HandleKey);
1043 emscripten_set_keyup_callback(keyElement, data, 0, Emscripten_HandleKey);
1044 emscripten_set_keypress_callback(keyElement, data, 0, Emscripten_HandleKeyPress);
1045 }
1046
1047 emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, data, 0, Emscripten_HandleFullscreenChange);
1048
1049 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, data, 0, Emscripten_HandleResize);
1050
1051 emscripten_set_visibilitychange_callback(data, 0, Emscripten_HandleVisibilityChange);
1052
1053 emscripten_set_beforeunload_callback(data, Emscripten_HandleBeforeUnload);
1054
1055 // !!! FIXME: currently Emscripten doesn't have a Pointer Events functions like emscripten_set_*_callback, but we should use those when they do:
1056 // !!! FIXME: https://github.com/emscripten-core/emscripten/issues/7278#issuecomment-2280024621
1057 Emscripten_set_pointer_event_callbacks(data);
1058
1059 // !!! FIXME: currently Emscripten doesn't have a Drop Events functions like emscripten_set_*_callback, but we should use those when they do:
1060 Emscripten_set_drag_event_callbacks(data);
1061}
1062
1063void Emscripten_UnregisterEventHandlers(SDL_WindowData *data)
1064{
1065 const char *target;
1066
1067 // !!! FIXME: currently Emscripten doesn't have a Drop Events functions like emscripten_set_*_callback, but we should use those when they do:
1068 Emscripten_unset_drag_event_callbacks(data);
1069
1070 // !!! FIXME: currently Emscripten doesn't have a Pointer Events functions like emscripten_set_*_callback, but we should use those when they do:
1071 // !!! FIXME: https://github.com/emscripten-core/emscripten/issues/7278#issuecomment-2280024621
1072 Emscripten_unset_pointer_event_callbacks(data);
1073
1074 // only works due to having one window
1075 emscripten_set_mousemove_callback(data->canvas_id, NULL, 0, NULL);
1076
1077 emscripten_set_mousedown_callback(data->canvas_id, NULL, 0, NULL);
1078 emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL);
1079
1080 emscripten_set_mouseenter_callback(data->canvas_id, NULL, 0, NULL);
1081 emscripten_set_mouseleave_callback(data->canvas_id, NULL, 0, NULL);
1082
1083 emscripten_set_wheel_callback(data->canvas_id, NULL, 0, NULL);
1084
1085 emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
1086 emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
1087
1088 emscripten_set_orientationchange_callback(NULL, 0, NULL);
1089
1090 emscripten_set_touchstart_callback(data->canvas_id, NULL, 0, NULL);
1091 emscripten_set_touchend_callback(data->canvas_id, NULL, 0, NULL);
1092 emscripten_set_touchmove_callback(data->canvas_id, NULL, 0, NULL);
1093 emscripten_set_touchcancel_callback(data->canvas_id, NULL, 0, NULL);
1094
1095 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL);
1096
1097 target = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT);
1098 if (!target || !*target) {
1099 target = EMSCRIPTEN_EVENT_TARGET_WINDOW;
1100 }
1101
1102 if (SDL_strcmp(target, "#none") != 0) {
1103 emscripten_set_keydown_callback(target, NULL, 0, NULL);
1104 emscripten_set_keyup_callback(target, NULL, 0, NULL);
1105 emscripten_set_keypress_callback(target, NULL, 0, NULL);
1106 }
1107
1108 emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL);
1109
1110 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
1111
1112 emscripten_set_visibilitychange_callback(NULL, 0, NULL);
1113
1114 emscripten_set_beforeunload_callback(NULL, NULL);
1115}
1116
1117#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN
diff --git a/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenevents.h b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenevents.h
new file mode 100644
index 0000000..fcfacec
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenevents.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_emscriptenevents_h_
23#define SDL_emscriptenevents_h_
24
25#include "SDL_emscriptenvideo.h"
26
27extern void Emscripten_RegisterEventHandlers(SDL_WindowData *data);
28extern void Emscripten_UnregisterEventHandlers(SDL_WindowData *data);
29extern EM_BOOL Emscripten_HandleCanvasResize(int eventType, const void *reserved, void *userData);
30
31#endif // SDL_emscriptenevents_h_
diff --git a/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenframebuffer.c b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenframebuffer.c
new file mode 100644
index 0000000..503fac6
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenframebuffer.c
@@ -0,0 +1,161 @@
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_EMSCRIPTEN
24
25#include "SDL_emscriptenvideo.h"
26#include "SDL_emscriptenframebuffer.h"
27
28#include <emscripten/threading.h>
29
30bool Emscripten_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, SDL_PixelFormat *format, void **pixels, int *pitch)
31{
32 SDL_Surface *surface;
33 const SDL_PixelFormat surface_format = SDL_PIXELFORMAT_XBGR8888;
34 int w, h;
35
36 // Free the old framebuffer surface
37 SDL_WindowData *data = window->internal;
38 surface = data->surface;
39 SDL_DestroySurface(surface);
40
41 // Create a new one
42 SDL_GetWindowSizeInPixels(window, &w, &h);
43
44 surface = SDL_CreateSurface(w, h, surface_format);
45 if (!surface) {
46 return false;
47 }
48
49 // Save the info and return!
50 data->surface = surface;
51 *format = surface_format;
52 *pixels = surface->pixels;
53 *pitch = surface->pitch;
54 return true;
55}
56
57bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects)
58{
59 SDL_Surface *surface;
60
61 SDL_WindowData *data = window->internal;
62 surface = data->surface;
63 if (!surface) {
64 return SDL_SetError("Couldn't find framebuffer surface for window");
65 }
66
67 // Send the data to the display
68
69 /* *INDENT-OFF* */ // clang-format off
70 MAIN_THREAD_EM_ASM({
71 var w = $0;
72 var h = $1;
73 var pixels = $2;
74 var canvasId = UTF8ToString($3);
75 var canvas = document.querySelector(canvasId);
76
77 //TODO: this should store a context per canvas
78 if (!Module['SDL3']) Module['SDL3'] = {};
79 var SDL3 = Module['SDL3'];
80 if (SDL3.ctxCanvas !== canvas) {
81 SDL3.ctx = Module['createContext'](canvas, false, true);
82 SDL3.ctxCanvas = canvas;
83 }
84 if (SDL3.w !== w || SDL3.h !== h || SDL3.imageCtx !== SDL3.ctx) {
85 SDL3.image = SDL3.ctx.createImageData(w, h);
86 SDL3.w = w;
87 SDL3.h = h;
88 SDL3.imageCtx = SDL3.ctx;
89 }
90 var data = SDL3.image.data;
91 var src = pixels / 4;
92 var dst = 0;
93 var num;
94
95 if (SDL3.data32Data !== data) {
96 SDL3.data32 = new Int32Array(data.buffer);
97 SDL3.data8 = new Uint8Array(data.buffer);
98 SDL3.data32Data = data;
99 }
100 var data32 = SDL3.data32;
101 num = data32.length;
102 // logically we need to do
103 // while (dst < num) {
104 // data32[dst++] = HEAP32[src++] | 0xff000000
105 // }
106 // the following code is faster though, because
107 // .set() is almost free - easily 10x faster due to
108 // native SDL_memcpy efficiencies, and the remaining loop
109 // just stores, not load + store, so it is faster
110 data32.set(HEAP32.subarray(src, src + num));
111 var data8 = SDL3.data8;
112 var i = 3;
113 var j = i + 4*num;
114 if (num % 8 == 0) {
115 // unrolling gives big speedups
116 while (i < j) {
117 data8[i] = 0xff;
118 i = i + 4 | 0;
119 data8[i] = 0xff;
120 i = i + 4 | 0;
121 data8[i] = 0xff;
122 i = i + 4 | 0;
123 data8[i] = 0xff;
124 i = i + 4 | 0;
125 data8[i] = 0xff;
126 i = i + 4 | 0;
127 data8[i] = 0xff;
128 i = i + 4 | 0;
129 data8[i] = 0xff;
130 i = i + 4 | 0;
131 data8[i] = 0xff;
132 i = i + 4 | 0;
133 }
134 } else {
135 while (i < j) {
136 data8[i] = 0xff;
137 i = i + 4 | 0;
138 }
139 }
140
141 SDL3.ctx.putImageData(SDL3.image, 0, 0);
142 }, surface->w, surface->h, surface->pixels, data->canvas_id);
143 /* *INDENT-ON* */ // clang-format on
144
145 if (emscripten_has_asyncify() && SDL_GetHintBoolean(SDL_HINT_EMSCRIPTEN_ASYNCIFY, true)) {
146 // give back control to browser for screen refresh
147 emscripten_sleep(0);
148 }
149
150 return true;
151}
152
153void Emscripten_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window)
154{
155 SDL_WindowData *data = window->internal;
156
157 SDL_DestroySurface(data->surface);
158 data->surface = NULL;
159}
160
161#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN
diff --git a/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenframebuffer.h b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenframebuffer.h
new file mode 100644
index 0000000..1be7079
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenframebuffer.h
@@ -0,0 +1,30 @@
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_emscriptenframebuffer_h_
24#define SDL_emscriptenframebuffer_h_
25
26extern bool Emscripten_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, SDL_PixelFormat *format, void **pixels, int *pitch);
27extern bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects);
28extern void Emscripten_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window);
29
30#endif // SDL_emscriptenframebuffer_h_
diff --git a/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenmouse.c b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenmouse.c
new file mode 100644
index 0000000..c959804
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenmouse.c
@@ -0,0 +1,216 @@
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_EMSCRIPTEN
24
25#include <emscripten/emscripten.h>
26#include <emscripten/html5.h>
27#include <emscripten/threading.h>
28
29#include "SDL_emscriptenmouse.h"
30#include "SDL_emscriptenvideo.h"
31
32#include "../SDL_video_c.h"
33#include "../../events/SDL_mouse_c.h"
34
35// older Emscriptens don't have this, but we need to for wasm64 compatibility.
36#ifndef MAIN_THREAD_EM_ASM_PTR
37 #ifdef __wasm64__
38 #error You need to upgrade your Emscripten compiler to support wasm64
39 #else
40 #define MAIN_THREAD_EM_ASM_PTR MAIN_THREAD_EM_ASM_INT
41 #endif
42#endif
43
44static SDL_Cursor *Emscripten_CreateCursorFromString(const char *cursor_str, bool is_custom)
45{
46 SDL_CursorData *curdata;
47 SDL_Cursor *cursor = SDL_calloc(1, sizeof(SDL_Cursor));
48 if (cursor) {
49 curdata = (SDL_CursorData *)SDL_calloc(1, sizeof(*curdata));
50 if (!curdata) {
51 SDL_free(cursor);
52 return NULL;
53 }
54
55 curdata->system_cursor = cursor_str;
56 curdata->is_custom = is_custom;
57 cursor->internal = curdata;
58 }
59
60 return cursor;
61}
62
63static SDL_Cursor *Emscripten_CreateDefaultCursor(void)
64{
65 SDL_SystemCursor id = SDL_GetDefaultSystemCursor();
66 const char *cursor_name = SDL_GetCSSCursorName(id, NULL);
67 return Emscripten_CreateCursorFromString(cursor_name, false);
68}
69
70EM_JS_DEPS(sdlmouse, "$stringToUTF8,$UTF8ToString");
71
72static SDL_Cursor *Emscripten_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
73{
74 const char *cursor_url = NULL;
75 SDL_Surface *conv_surf;
76
77 conv_surf = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ABGR8888);
78
79 if (!conv_surf) {
80 return NULL;
81 }
82
83 /* *INDENT-OFF* */ // clang-format off
84 cursor_url = (const char *)MAIN_THREAD_EM_ASM_PTR({
85 var w = $0;
86 var h = $1;
87 var hot_x = $2;
88 var hot_y = $3;
89 var pixels = $4;
90
91 var canvas = document.createElement("canvas");
92 canvas.width = w;
93 canvas.height = h;
94
95 var ctx = canvas.getContext("2d");
96
97 var image = ctx.createImageData(w, h);
98 var data = image.data;
99 var src = pixels / 4;
100
101 var data32 = new Int32Array(data.buffer);
102 data32.set(HEAP32.subarray(src, src + data32.length));
103
104 ctx.putImageData(image, 0, 0);
105 var url = hot_x === 0 && hot_y === 0
106 ? "url(" + canvas.toDataURL() + "), auto"
107 : "url(" + canvas.toDataURL() + ") " + hot_x + " " + hot_y + ", auto";
108
109 var urlBuf = _SDL_malloc(url.length + 1);
110 stringToUTF8(url, urlBuf, url.length + 1);
111
112 return urlBuf;
113 }, surface->w, surface->h, hot_x, hot_y, conv_surf->pixels);
114 /* *INDENT-ON* */ // clang-format on
115
116 SDL_DestroySurface(conv_surf);
117
118 return Emscripten_CreateCursorFromString(cursor_url, true);
119}
120
121static SDL_Cursor *Emscripten_CreateSystemCursor(SDL_SystemCursor id)
122{
123 const char *cursor_name = SDL_GetCSSCursorName(id, NULL);
124
125 return Emscripten_CreateCursorFromString(cursor_name, false);
126}
127
128static void Emscripten_FreeCursor(SDL_Cursor *cursor)
129{
130 SDL_CursorData *curdata;
131 if (cursor) {
132 curdata = cursor->internal;
133
134 if (curdata) {
135 if (curdata->is_custom) {
136 SDL_free((char *)curdata->system_cursor);
137 }
138 SDL_free(cursor->internal);
139 }
140
141 SDL_free(cursor);
142 }
143}
144
145static bool Emscripten_ShowCursor(SDL_Cursor *cursor)
146{
147 SDL_CursorData *curdata;
148 if (SDL_GetMouseFocus() != NULL) {
149 if (cursor && cursor->internal) {
150 curdata = cursor->internal;
151
152 if (curdata->system_cursor) {
153 /* *INDENT-OFF* */ // clang-format off
154 MAIN_THREAD_EM_ASM({
155 if (Module['canvas']) {
156 Module['canvas'].style['cursor'] = UTF8ToString($0);
157 }
158 }, curdata->system_cursor);
159 /* *INDENT-ON* */ // clang-format on
160 }
161 } else {
162 /* *INDENT-OFF* */ // clang-format off
163 MAIN_THREAD_EM_ASM(
164 if (Module['canvas']) {
165 Module['canvas'].style['cursor'] = 'none';
166 }
167 );
168 /* *INDENT-ON* */ // clang-format on
169 }
170 }
171 return true;
172}
173
174static bool Emscripten_SetRelativeMouseMode(bool enabled)
175{
176 SDL_Window *window;
177 SDL_WindowData *window_data;
178
179 // TODO: pointer lock isn't actually enabled yet
180 if (enabled) {
181 window = SDL_GetMouseFocus();
182 if (!window) {
183 return false;
184 }
185
186 window_data = window->internal;
187
188 if (emscripten_request_pointerlock(window_data->canvas_id, 1) >= EMSCRIPTEN_RESULT_SUCCESS) {
189 return true;
190 }
191 } else {
192 if (emscripten_exit_pointerlock() >= EMSCRIPTEN_RESULT_SUCCESS) {
193 return true;
194 }
195 }
196 return false;
197}
198
199void Emscripten_InitMouse(void)
200{
201 SDL_Mouse *mouse = SDL_GetMouse();
202
203 mouse->CreateCursor = Emscripten_CreateCursor;
204 mouse->ShowCursor = Emscripten_ShowCursor;
205 mouse->FreeCursor = Emscripten_FreeCursor;
206 mouse->CreateSystemCursor = Emscripten_CreateSystemCursor;
207 mouse->SetRelativeMouseMode = Emscripten_SetRelativeMouseMode;
208
209 SDL_SetDefaultCursor(Emscripten_CreateDefaultCursor());
210}
211
212void Emscripten_QuitMouse(void)
213{
214}
215
216#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN
diff --git a/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenmouse.h b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenmouse.h
new file mode 100644
index 0000000..5fcfeb5
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenmouse.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#ifndef SDL_emscriptenmouse_h_
23#define SDL_emscriptenmouse_h_
24
25struct SDL_CursorData
26{
27 const char *system_cursor;
28 bool is_custom;
29};
30
31extern void Emscripten_InitMouse(void);
32extern void Emscripten_QuitMouse(void);
33
34#endif // SDL_emscriptenmouse_h_
diff --git a/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenopengles.c b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenopengles.c
new file mode 100644
index 0000000..227cdc5
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenopengles.c
@@ -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#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_EMSCRIPTEN
24
25#include <emscripten/emscripten.h>
26#include <emscripten/html5_webgl.h>
27#include <GLES2/gl2.h>
28
29#include "SDL_emscriptenvideo.h"
30#include "SDL_emscriptenopengles.h"
31
32bool Emscripten_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path)
33{
34 return true;
35}
36
37void Emscripten_GLES_UnloadLibrary(SDL_VideoDevice *_this)
38{
39}
40
41SDL_FunctionPointer Emscripten_GLES_GetProcAddress(SDL_VideoDevice *_this, const char *proc)
42{
43 return emscripten_webgl_get_proc_address(proc);
44}
45
46bool Emscripten_GLES_SetSwapInterval(SDL_VideoDevice *_this, int interval)
47{
48 if (interval < 0) {
49 return SDL_SetError("Late swap tearing currently unsupported");
50 }
51
52 if (Emscripten_ShouldSetSwapInterval(interval)) {
53 if (interval == 0) {
54 emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, 0);
55 } else {
56 emscripten_set_main_loop_timing(EM_TIMING_RAF, interval);
57 }
58 }
59
60 return true;
61}
62
63bool Emscripten_GLES_GetSwapInterval(SDL_VideoDevice *_this, int *interval)
64{
65 int mode, value;
66
67 emscripten_get_main_loop_timing(&mode, &value);
68
69 if (mode == EM_TIMING_RAF) {
70 *interval = value;
71 return true;
72 } else {
73 *interval = 0;
74 return true;
75 }
76}
77
78SDL_GLContext Emscripten_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window)
79{
80 SDL_WindowData *window_data;
81
82 EmscriptenWebGLContextAttributes attribs;
83 EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;
84
85 emscripten_webgl_init_context_attributes(&attribs);
86
87 attribs.alpha = _this->gl_config.alpha_size > 0;
88 attribs.depth = _this->gl_config.depth_size > 0;
89 attribs.stencil = _this->gl_config.stencil_size > 0;
90 attribs.antialias = _this->gl_config.multisamplebuffers == 1;
91
92 if (_this->gl_config.major_version == 3)
93 attribs.majorVersion = 2; // WebGL 2.0 ~= GLES 3.0
94
95 window_data = window->internal;
96
97 if (window_data->gl_context) {
98 SDL_SetError("Cannot create multiple webgl contexts per window");
99 return NULL;
100 }
101
102 context = emscripten_webgl_create_context(window_data->canvas_id, &attribs);
103
104 if (context < 0) {
105 SDL_SetError("Could not create webgl context");
106 return NULL;
107 }
108
109 if (emscripten_webgl_make_context_current(context) != EMSCRIPTEN_RESULT_SUCCESS) {
110 emscripten_webgl_destroy_context(context);
111 return NULL;
112 }
113
114 window_data->gl_context = (SDL_GLContext)context;
115
116 return (SDL_GLContext)context;
117}
118
119bool Emscripten_GLES_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context)
120{
121 SDL_Window *window;
122
123 // remove the context from its window
124 for (window = _this->windows; window; window = window->next) {
125 SDL_WindowData *window_data = window->internal;
126
127 if (window_data->gl_context == context) {
128 window_data->gl_context = NULL;
129 }
130 }
131
132 emscripten_webgl_destroy_context((EMSCRIPTEN_WEBGL_CONTEXT_HANDLE)context);
133 return true;
134}
135
136bool Emscripten_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
137{
138 if (emscripten_has_asyncify() && SDL_GetHintBoolean(SDL_HINT_EMSCRIPTEN_ASYNCIFY, true)) {
139 // give back control to browser for screen refresh
140 emscripten_sleep(0);
141 }
142 return true;
143}
144
145bool Emscripten_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context)
146{
147 // it isn't possible to reuse contexts across canvases
148 if (window && context) {
149 SDL_WindowData *window_data = window->internal;
150
151 if (context != window_data->gl_context) {
152 return SDL_SetError("Cannot make context current to another window");
153 }
154 }
155
156 if (emscripten_webgl_make_context_current((EMSCRIPTEN_WEBGL_CONTEXT_HANDLE)context) != EMSCRIPTEN_RESULT_SUCCESS) {
157 return SDL_SetError("Unable to make context current");
158 }
159 return true;
160}
161
162#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN
diff --git a/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenopengles.h b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenopengles.h
new file mode 100644
index 0000000..d0af111
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenopengles.h
@@ -0,0 +1,43 @@
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_emscriptenopengles_h_
24#define SDL_emscriptenopengles_h_
25
26#ifdef SDL_VIDEO_DRIVER_EMSCRIPTEN
27
28#include "../SDL_sysvideo.h"
29
30// OpenGLES functions
31extern bool Emscripten_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path);
32extern void Emscripten_GLES_UnloadLibrary(SDL_VideoDevice *_this);
33extern SDL_FunctionPointer Emscripten_GLES_GetProcAddress(SDL_VideoDevice *_this, const char *proc);
34extern bool Emscripten_GLES_SetSwapInterval(SDL_VideoDevice *_this, int interval);
35extern bool Emscripten_GLES_GetSwapInterval(SDL_VideoDevice *_this, int *interval);
36extern SDL_GLContext Emscripten_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window);
37extern bool Emscripten_GLES_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context);
38extern bool Emscripten_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window);
39extern bool Emscripten_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context);
40
41#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN
42
43#endif // SDL_emscriptenopengles_h_
diff --git a/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.c b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.c
new file mode 100644
index 0000000..1496268
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.c
@@ -0,0 +1,459 @@
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_EMSCRIPTEN
24
25#include "../SDL_sysvideo.h"
26#include "../SDL_pixels_c.h"
27#include "../../events/SDL_events_c.h"
28
29#include "SDL_emscriptenvideo.h"
30#include "SDL_emscriptenopengles.h"
31#include "SDL_emscriptenframebuffer.h"
32#include "SDL_emscriptenevents.h"
33#include "SDL_emscriptenmouse.h"
34
35#define EMSCRIPTENVID_DRIVER_NAME "emscripten"
36
37// Initialization/Query functions
38static bool Emscripten_VideoInit(SDL_VideoDevice *_this);
39static bool Emscripten_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode);
40static void Emscripten_VideoQuit(SDL_VideoDevice *_this);
41static bool Emscripten_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect);
42
43static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props);
44static void Emscripten_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window);
45static void Emscripten_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h);
46static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
47static SDL_FullscreenResult Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen);
48static void Emscripten_PumpEvents(SDL_VideoDevice *_this);
49static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window);
50
51static bool pumpevents_has_run = false;
52static int pending_swap_interval = -1;
53
54
55// Emscripten driver bootstrap functions
56
57static void Emscripten_DeleteDevice(SDL_VideoDevice *device)
58{
59 SDL_free(device);
60}
61
62static SDL_SystemTheme Emscripten_GetSystemTheme(void)
63{
64 /* Technically, light theme can mean explicit light theme or no preference.
65 https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme#syntax */
66
67 int theme_code = EM_ASM_INT({
68 if (!window.matchMedia) {
69 return -1;
70 }
71
72 if (window.matchMedia('(prefers-color-scheme: light)').matches) {
73 return 0;
74 }
75
76 if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
77 return 1;
78 }
79
80 return -1;
81 });
82
83 switch (theme_code) {
84 case 0:
85 return SDL_SYSTEM_THEME_LIGHT;
86
87 case 1:
88 return SDL_SYSTEM_THEME_DARK;
89
90 default:
91 return SDL_SYSTEM_THEME_UNKNOWN;
92 }
93}
94
95static void Emscripten_ListenSystemTheme(void)
96{
97 MAIN_THREAD_EM_ASM({
98 if (window.matchMedia) {
99 if (typeof(Module['SDL3']) === 'undefined') {
100 Module['SDL3'] = {};
101 }
102
103 var SDL3 = Module['SDL3'];
104
105 SDL3.eventHandlerThemeChanged = function(event) {
106 _Emscripten_SendSystemThemeChangedEvent();
107 };
108
109 SDL3.themeChangedMatchMedia = window.matchMedia('(prefers-color-scheme: dark)');
110 SDL3.themeChangedMatchMedia.addEventListener('change', SDL3.eventHandlerThemeChanged);
111 }
112 });
113}
114
115static void Emscripten_UnlistenSystemTheme(void)
116{
117 MAIN_THREAD_EM_ASM({
118 if (typeof(Module['SDL3']) !== 'undefined') {
119 var SDL3 = Module['SDL3'];
120
121 SDL3.themeChangedMatchMedia.removeEventListener('change', SDL3.eventHandlerThemeChanged);
122 SDL3.themeChangedMatchMedia = undefined;
123 SDL3.eventHandlerThemeChanged = undefined;
124 }
125 });
126}
127
128EMSCRIPTEN_KEEPALIVE void Emscripten_SendSystemThemeChangedEvent(void)
129{
130 SDL_SetSystemTheme(Emscripten_GetSystemTheme());
131}
132
133static SDL_VideoDevice *Emscripten_CreateDevice(void)
134{
135 SDL_VideoDevice *device;
136
137 // Initialize all variables that we clean on shutdown
138 device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice));
139 if (!device) {
140 return NULL;
141 }
142
143 /* Firefox sends blur event which would otherwise prevent full screen
144 * when the user clicks to allow full screen.
145 * See https://bugzilla.mozilla.org/show_bug.cgi?id=1144964
146 */
147 SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
148
149 // Set the function pointers
150 device->VideoInit = Emscripten_VideoInit;
151 device->VideoQuit = Emscripten_VideoQuit;
152 device->GetDisplayUsableBounds = Emscripten_GetDisplayUsableBounds;
153 device->SetDisplayMode = Emscripten_SetDisplayMode;
154
155 device->PumpEvents = Emscripten_PumpEvents;
156
157 device->CreateSDLWindow = Emscripten_CreateWindow;
158 device->SetWindowTitle = Emscripten_SetWindowTitle;
159 /*device->SetWindowIcon = Emscripten_SetWindowIcon;
160 device->SetWindowPosition = Emscripten_SetWindowPosition;*/
161 device->SetWindowSize = Emscripten_SetWindowSize;
162 /*device->ShowWindow = Emscripten_ShowWindow;
163 device->HideWindow = Emscripten_HideWindow;
164 device->RaiseWindow = Emscripten_RaiseWindow;
165 device->MaximizeWindow = Emscripten_MaximizeWindow;
166 device->MinimizeWindow = Emscripten_MinimizeWindow;
167 device->RestoreWindow = Emscripten_RestoreWindow;
168 device->SetWindowMouseGrab = Emscripten_SetWindowMouseGrab;*/
169 device->GetWindowSizeInPixels = Emscripten_GetWindowSizeInPixels;
170 device->DestroyWindow = Emscripten_DestroyWindow;
171 device->SetWindowFullscreen = Emscripten_SetWindowFullscreen;
172
173 device->CreateWindowFramebuffer = Emscripten_CreateWindowFramebuffer;
174 device->UpdateWindowFramebuffer = Emscripten_UpdateWindowFramebuffer;
175 device->DestroyWindowFramebuffer = Emscripten_DestroyWindowFramebuffer;
176
177 device->GL_LoadLibrary = Emscripten_GLES_LoadLibrary;
178 device->GL_GetProcAddress = Emscripten_GLES_GetProcAddress;
179 device->GL_UnloadLibrary = Emscripten_GLES_UnloadLibrary;
180 device->GL_CreateContext = Emscripten_GLES_CreateContext;
181 device->GL_MakeCurrent = Emscripten_GLES_MakeCurrent;
182 device->GL_SetSwapInterval = Emscripten_GLES_SetSwapInterval;
183 device->GL_GetSwapInterval = Emscripten_GLES_GetSwapInterval;
184 device->GL_SwapWindow = Emscripten_GLES_SwapWindow;
185 device->GL_DestroyContext = Emscripten_GLES_DestroyContext;
186
187 device->free = Emscripten_DeleteDevice;
188
189 Emscripten_ListenSystemTheme();
190 device->system_theme = Emscripten_GetSystemTheme();
191
192 return device;
193}
194
195VideoBootStrap Emscripten_bootstrap = {
196 EMSCRIPTENVID_DRIVER_NAME, "SDL emscripten video driver",
197 Emscripten_CreateDevice,
198 NULL, // no ShowMessageBox implementation
199 false
200};
201
202bool Emscripten_VideoInit(SDL_VideoDevice *_this)
203{
204 SDL_DisplayMode mode;
205
206 // Use a fake 32-bpp desktop mode
207 SDL_zero(mode);
208 mode.format = SDL_PIXELFORMAT_XRGB8888;
209 emscripten_get_screen_size(&mode.w, &mode.h);
210 mode.pixel_density = emscripten_get_device_pixel_ratio();
211
212 if (SDL_AddBasicVideoDisplay(&mode) == 0) {
213 return false;
214 }
215
216 Emscripten_InitMouse();
217
218 // Assume we have a mouse and keyboard
219 SDL_AddKeyboard(SDL_DEFAULT_KEYBOARD_ID, NULL, false);
220 SDL_AddMouse(SDL_DEFAULT_MOUSE_ID, NULL, false);
221
222 // We're done!
223 return true;
224}
225
226static bool Emscripten_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
227{
228 // can't do this
229 return true;
230}
231
232static void Emscripten_VideoQuit(SDL_VideoDevice *_this)
233{
234 Emscripten_QuitMouse();
235 Emscripten_UnlistenSystemTheme();
236 pumpevents_has_run = false;
237 pending_swap_interval = -1;
238}
239
240static bool Emscripten_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
241{
242 if (rect) {
243 rect->x = 0;
244 rect->y = 0;
245 rect->w = MAIN_THREAD_EM_ASM_INT({
246 return window.innerWidth;
247 });
248 rect->h = MAIN_THREAD_EM_ASM_INT({
249 return window.innerHeight;
250 });
251 }
252 return true;
253}
254
255bool Emscripten_ShouldSetSwapInterval(int interval)
256{
257 if (!pumpevents_has_run) {
258 pending_swap_interval = interval;
259 return false;
260 }
261 return true;
262}
263
264static void Emscripten_PumpEvents(SDL_VideoDevice *_this)
265{
266 if (!pumpevents_has_run) {
267 // we assume you've set a mainloop by the time you've called pumpevents, so we delay initial SetInterval changes until then.
268 // otherwise you'll get a warning on the javascript console.
269 pumpevents_has_run = true;
270 if (pending_swap_interval >= 0) {
271 Emscripten_GLES_SetSwapInterval(_this, pending_swap_interval);
272 pending_swap_interval = -1;
273 }
274 }
275}
276
277EMSCRIPTEN_KEEPALIVE void requestFullscreenThroughSDL(SDL_Window *window)
278{
279 SDL_SetWindowFullscreen(window, true);
280}
281
282static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
283{
284 SDL_WindowData *wdata;
285 double scaled_w, scaled_h;
286 double css_w, css_h;
287 const char *selector;
288
289 // Allocate window internal data
290 wdata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData));
291 if (!wdata) {
292 return false;
293 }
294
295 selector = SDL_GetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR);
296 if (!selector) {
297 selector = "#canvas";
298 }
299
300 wdata->canvas_id = SDL_strdup(selector);
301
302 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
303 wdata->pixel_ratio = emscripten_get_device_pixel_ratio();
304 } else {
305 wdata->pixel_ratio = 1.0f;
306 }
307
308 scaled_w = SDL_floor(window->w * wdata->pixel_ratio);
309 scaled_h = SDL_floor(window->h * wdata->pixel_ratio);
310
311 // set a fake size to check if there is any CSS sizing the canvas
312 emscripten_set_canvas_element_size(wdata->canvas_id, 1, 1);
313 emscripten_get_element_css_size(wdata->canvas_id, &css_w, &css_h);
314
315 wdata->external_size = SDL_floor(css_w) != 1 || SDL_floor(css_h) != 1;
316
317 if ((window->flags & SDL_WINDOW_RESIZABLE) && wdata->external_size) {
318 // external css has resized us
319 scaled_w = css_w * wdata->pixel_ratio;
320 scaled_h = css_h * wdata->pixel_ratio;
321
322 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(css_w), SDL_lroundf(css_h));
323 }
324 emscripten_set_canvas_element_size(wdata->canvas_id, SDL_lroundf(scaled_w), SDL_lroundf(scaled_h));
325
326 // if the size is not being controlled by css, we need to scale down for hidpi
327 if (!wdata->external_size) {
328 if (wdata->pixel_ratio != 1.0f) {
329 // scale canvas down
330 emscripten_set_element_css_size(wdata->canvas_id, window->w, window->h);
331 }
332 }
333
334 wdata->window = window;
335
336 // Setup driver data for this window
337 window->internal = wdata;
338
339 // One window, it always has focus
340 SDL_SetMouseFocus(window);
341 SDL_SetKeyboardFocus(window);
342
343 Emscripten_RegisterEventHandlers(wdata);
344
345 // disable the emscripten "fullscreen" button.
346 MAIN_THREAD_EM_ASM({
347 Module['requestFullscreen'] = function(lockPointer, resizeCanvas) {
348 _requestFullscreenThroughSDL($0);
349 };
350 }, window);
351
352 // Window has been successfully created
353 return true;
354}
355
356static void Emscripten_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
357{
358 SDL_WindowData *data;
359
360 if (window->internal) {
361 data = window->internal;
362 // update pixel ratio
363 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
364 data->pixel_ratio = emscripten_get_device_pixel_ratio();
365 }
366 emscripten_set_canvas_element_size(data->canvas_id, SDL_lroundf(window->pending.w * data->pixel_ratio), SDL_lroundf(window->pending.h * data->pixel_ratio));
367
368 // scale canvas down
369 if (!data->external_size && data->pixel_ratio != 1.0f) {
370 emscripten_set_element_css_size(data->canvas_id, window->pending.w, window->pending.h);
371 }
372
373 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->pending.w, window->pending.h);
374 }
375}
376
377static void Emscripten_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h)
378{
379 SDL_WindowData *data;
380 if (window->internal) {
381 data = window->internal;
382 *w = SDL_lroundf(window->w * data->pixel_ratio);
383 *h = SDL_lroundf(window->h * data->pixel_ratio);
384 }
385}
386
387static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
388{
389 SDL_WindowData *data;
390
391 if (window->internal) {
392 data = window->internal;
393
394 Emscripten_UnregisterEventHandlers(data);
395
396 // We can't destroy the canvas, so resize it to zero instead
397 emscripten_set_canvas_element_size(data->canvas_id, 0, 0);
398 SDL_free(data->canvas_id);
399
400 SDL_free(window->internal);
401 window->internal = NULL;
402 }
403
404 // just ignore clicks on the fullscreen button while there's no SDL window.
405 MAIN_THREAD_EM_ASM({ Module['requestFullscreen'] = function(lockPointer, resizeCanvas) {}; });
406}
407
408static SDL_FullscreenResult Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)
409{
410 SDL_WindowData *data;
411 int res = -1;
412
413 if (window->internal) {
414 data = window->internal;
415
416 if (fullscreen) {
417 EmscriptenFullscreenStrategy strategy;
418 bool is_fullscreen_desktop = !window->fullscreen_exclusive;
419
420 SDL_zero(strategy);
421 strategy.scaleMode = is_fullscreen_desktop ? EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH : EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT;
422
423 if (!is_fullscreen_desktop) {
424 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE;
425 } else if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
426 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF;
427 } else {
428 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
429 }
430
431 strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
432
433 strategy.canvasResizedCallback = Emscripten_HandleCanvasResize;
434 strategy.canvasResizedCallbackUserData = data;
435
436 data->fullscreen_mode_flags = (window->flags & SDL_WINDOW_FULLSCREEN);
437 data->fullscreen_resize = is_fullscreen_desktop;
438
439 res = emscripten_request_fullscreen_strategy(data->canvas_id, 1, &strategy);
440 } else {
441 res = emscripten_exit_fullscreen();
442 }
443 }
444
445 if (res == EMSCRIPTEN_RESULT_SUCCESS) {
446 return SDL_FULLSCREEN_SUCCEEDED;
447 } else if (res == EMSCRIPTEN_RESULT_DEFERRED) {
448 return SDL_FULLSCREEN_PENDING;
449 } else {
450 return SDL_FULLSCREEN_FAILED;
451 }
452}
453
454static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
455{
456 emscripten_set_window_title(window->title);
457}
458
459#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN
diff --git a/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.h b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.h
new file mode 100644
index 0000000..892d074
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.h
@@ -0,0 +1,54 @@
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_emscriptenvideo_h_
24#define SDL_emscriptenvideo_h_
25
26#include "../SDL_sysvideo.h"
27#include "../../events/SDL_touch_c.h"
28#include <emscripten/emscripten.h>
29#include <emscripten/html5.h>
30
31struct SDL_WindowData
32{
33 SDL_Window *window;
34 SDL_Surface *surface;
35
36 SDL_GLContext gl_context;
37
38 char *canvas_id;
39
40 float pixel_ratio;
41
42 bool external_size;
43
44 Uint32 fullscreen_mode_flags;
45 bool fullscreen_resize;
46
47 bool has_pointer_lock;
48
49 bool mouse_focus_loss_pending;
50};
51
52bool Emscripten_ShouldSetSwapInterval(int interval);
53
54#endif // SDL_emscriptenvideo_h_