summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.c')
-rw-r--r--contrib/SDL-3.2.8/src/video/emscripten/SDL_emscriptenvideo.c459
1 files changed, 459 insertions, 0 deletions
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