Atlas - SDL_emscriptenvideo.c

Home / ext / SDL / src / video / emscripten Lines: 3 | Size: 30555 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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#include "../../SDL_hints_c.h" 29 30#include "SDL_emscriptenvideo.h" 31#include "SDL_emscriptenopengles.h" 32#include "SDL_emscriptenframebuffer.h" 33#include "SDL_emscriptenevents.h" 34#include "SDL_emscriptenmouse.h" 35 36#define EMSCRIPTENVID_DRIVER_NAME "emscripten" 37 38// Initialization/Query functions 39static bool Emscripten_VideoInit(SDL_VideoDevice *_this); 40static bool Emscripten_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); 41static void Emscripten_VideoQuit(SDL_VideoDevice *_this); 42static bool Emscripten_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect); 43 44static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); 45static void Emscripten_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); 46static void Emscripten_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window * window, bool resizable); 47static void Emscripten_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); 48static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); 49static SDL_FullscreenResult Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen); 50static void Emscripten_PumpEvents(SDL_VideoDevice *_this); 51static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); 52static bool Emscripten_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); 53static bool Emscripten_SetWindowFillDocument(SDL_VideoDevice *_this, SDL_Window *window, bool fill); 54 55SDL_Window *Emscripten_fill_document_window = NULL; 56 57static bool pumpevents_has_run = false; 58static int pending_swap_interval = -1; 59 60// Emscripten driver bootstrap functions 61 62static void Emscripten_DeleteDevice(SDL_VideoDevice *device) 63{ 64 SDL_free(device); 65} 66 67static SDL_SystemTheme Emscripten_GetSystemTheme(void) 68{ 69 /* Technically, light theme can mean explicit light theme or no preference. 70 https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme#syntax */ 71 72 int theme_code = MAIN_THREAD_EM_ASM_INT({ 73 if (!window.matchMedia) { 74 return -1; 75 } 76 77 if (window.matchMedia('(prefers-color-scheme: light)').matches) { 78 return 0; 79 } 80 81 if (window.matchMedia('(prefers-color-scheme: dark)').matches) { 82 return 1; 83 } 84 85 return -1; 86 }); 87 88 switch (theme_code) { 89 case 0: 90 return SDL_SYSTEM_THEME_LIGHT; 91 92 case 1: 93 return SDL_SYSTEM_THEME_DARK; 94 95 default: 96 return SDL_SYSTEM_THEME_UNKNOWN; 97 } 98} 99 100static void Emscripten_ListenSystemTheme(void) 101{ 102 MAIN_THREAD_EM_ASM({ 103 if (window.matchMedia) { 104 if (typeof(Module['SDL3']) === 'undefined') { 105 Module['SDL3'] = {}; 106 } 107 108 var SDL3 = Module['SDL3']; 109 110 SDL3.eventHandlerThemeChanged = function(event) { 111 _Emscripten_SendSystemThemeChangedEvent(); 112 }; 113 114 SDL3.themeChangedMatchMedia = window.matchMedia('(prefers-color-scheme: dark)'); 115 SDL3.themeChangedMatchMedia.addEventListener('change', SDL3.eventHandlerThemeChanged); 116 } 117 }); 118} 119 120static void Emscripten_UnlistenSystemTheme(void) 121{ 122 MAIN_THREAD_EM_ASM({ 123 if (typeof(Module['SDL3']) !== 'undefined') { 124 var SDL3 = Module['SDL3']; 125 126 SDL3.themeChangedMatchMedia.removeEventListener('change', SDL3.eventHandlerThemeChanged); 127 SDL3.themeChangedMatchMedia = undefined; 128 SDL3.eventHandlerThemeChanged = undefined; 129 } 130 }); 131} 132 133EMSCRIPTEN_KEEPALIVE void Emscripten_SendSystemThemeChangedEvent(void) 134{ 135 SDL_SetSystemTheme(Emscripten_GetSystemTheme()); 136} 137 138static SDL_VideoDevice *Emscripten_CreateDevice(void) 139{ 140 SDL_VideoDevice *device; 141 142 // Initialize all variables that we clean on shutdown 143 device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); 144 if (!device) { 145 return NULL; 146 } 147 148 /* Firefox sends blur event which would otherwise prevent full screen 149 * when the user clicks to allow full screen. 150 * See https://bugzilla.mozilla.org/show_bug.cgi?id=1144964 151 */ 152 SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); 153 154 // Set the function pointers 155 device->VideoInit = Emscripten_VideoInit; 156 device->VideoQuit = Emscripten_VideoQuit; 157 device->GetDisplayUsableBounds = Emscripten_GetDisplayUsableBounds; 158 device->SetDisplayMode = Emscripten_SetDisplayMode; 159 160 device->PumpEvents = Emscripten_PumpEvents; 161 162 device->CreateSDLWindow = Emscripten_CreateWindow; 163 device->SetWindowTitle = Emscripten_SetWindowTitle; 164 device->SetWindowIcon = Emscripten_SetWindowIcon; 165 /*device->SetWindowPosition = Emscripten_SetWindowPosition;*/ 166 device->SetWindowSize = Emscripten_SetWindowSize; 167 device->SetWindowResizable = Emscripten_SetWindowResizable; 168 /*device->ShowWindow = Emscripten_ShowWindow; 169 device->HideWindow = Emscripten_HideWindow; 170 device->RaiseWindow = Emscripten_RaiseWindow; 171 device->MaximizeWindow = Emscripten_MaximizeWindow; 172 device->MinimizeWindow = Emscripten_MinimizeWindow; 173 device->RestoreWindow = Emscripten_RestoreWindow; 174 device->SetWindowMouseGrab = Emscripten_SetWindowMouseGrab;*/ 175 device->GetWindowSizeInPixels = Emscripten_GetWindowSizeInPixels; 176 device->DestroyWindow = Emscripten_DestroyWindow; 177 device->SetWindowFullscreen = Emscripten_SetWindowFullscreen; 178 device->SetWindowFillDocument = Emscripten_SetWindowFillDocument; 179 180 device->CreateWindowFramebuffer = Emscripten_CreateWindowFramebuffer; 181 device->UpdateWindowFramebuffer = Emscripten_UpdateWindowFramebuffer; 182 device->DestroyWindowFramebuffer = Emscripten_DestroyWindowFramebuffer; 183 184 device->GL_LoadLibrary = Emscripten_GLES_LoadLibrary; 185 device->GL_GetProcAddress = Emscripten_GLES_GetProcAddress; 186 device->GL_UnloadLibrary = Emscripten_GLES_UnloadLibrary; 187 device->GL_CreateContext = Emscripten_GLES_CreateContext; 188 device->GL_MakeCurrent = Emscripten_GLES_MakeCurrent; 189 device->GL_SetSwapInterval = Emscripten_GLES_SetSwapInterval; 190 device->GL_GetSwapInterval = Emscripten_GLES_GetSwapInterval; 191 device->GL_SwapWindow = Emscripten_GLES_SwapWindow; 192 device->GL_DestroyContext = Emscripten_GLES_DestroyContext; 193 194 device->free = Emscripten_DeleteDevice; 195 196 Emscripten_ListenSystemTheme(); 197 device->system_theme = Emscripten_GetSystemTheme(); 198 199 return device; 200} 201 202static bool Emscripten_ShowMessagebox(const SDL_MessageBoxData *messageboxdata, int *buttonID) { 203 if (emscripten_has_asyncify() && SDL_GetHintBoolean(SDL_HINT_EMSCRIPTEN_ASYNCIFY, true)) { 204 char dialog_background[32]; 205 char dialog_color[32]; 206 char button_border[32]; 207 char button_background[32]; 208 char button_hovered[32]; 209 210 if (messageboxdata->colorScheme) { 211 SDL_MessageBoxColor color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BACKGROUND]; 212 SDL_snprintf(dialog_background, sizeof(dialog_background), "rgb(%u, %u, %u)", color.r, color.g, color.b); 213 214 color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_TEXT]; 215 SDL_snprintf(dialog_color, sizeof(dialog_color), "rgb(%u, %u, %u)", color.r, color.g, color.b); 216 217 color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER]; 218 SDL_snprintf(button_border, sizeof(button_border), "rgb(%u, %u, %u)", color.r, color.g, color.b); 219 220 color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND]; 221 SDL_snprintf(button_background, sizeof(button_background), "rgb(%u, %u, %u)", color.r, color.g, color.b); 222 223 color = messageboxdata->colorScheme->colors[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED]; 224 SDL_snprintf(button_hovered, sizeof(button_hovered), "rgb(%u, %u, %u)", color.r, color.g, color.b); 225 } else { 226 SDL_zero(dialog_background); 227 SDL_zero(dialog_color); 228 SDL_zero(button_border); 229 SDL_zero(button_background); 230 SDL_zero(button_hovered); 231 } 232 233 // TODO: Handle parent window when multiple windows can be added in Emscripten builds 234 char dialog_id[64]; 235 SDL_snprintf(dialog_id, sizeof(dialog_id), "SDL3_messagebox_%u", SDL_rand_bits()); 236 EM_ASM({ 237 var title = UTF8ToString($0); 238 var message = UTF8ToString($1); 239 var background = UTF8ToString($2); 240 var color = UTF8ToString($3); 241 var id = UTF8ToString($4); 242 243 // Dialogs are always put in the front of the DOM 244 var dialog = document.createElement("dialog"); 245 // Set class to allow for CSS selectors 246 dialog.classList.add("SDL3_messagebox"); 247 dialog.id = id; 248 dialog.style.color = color; 249 dialog.style.backgroundColor = background; 250 document.body.append(dialog); 251 252 var h1 = document.createElement("h1"); 253 h1.innerText = title; 254 dialog.append(h1); 255 256 var p = document.createElement("p"); 257 p.innerText = message; 258 dialog.append(p); 259 260 dialog.showModal(); 261 }, messageboxdata->title, messageboxdata->message, dialog_background, dialog_color, dialog_id); 262 263 int i; 264 for (i = 0; i < messageboxdata->numbuttons; ++i) { 265 SDL_MessageBoxButtonData button = messageboxdata->buttons[i]; 266 267 const int created = EM_ASM_INT({ 268 var dialog_id = UTF8ToString($0); 269 var text = UTF8ToString($1); 270 var responseId = $2; 271 var clickOnReturn = $3; 272 var clickOnEscape = $4; 273 var border = UTF8ToString($5); 274 var background = UTF8ToString($6); 275 var hovered = UTF8ToString($7); 276 277 var dialog = document.getElementById(dialog_id); 278 if (!dialog) { 279 return false; 280 } 281 282 var button = document.createElement("button"); 283 button.innerText = text; 284 button.style.borderColor = border; 285 button.style.backgroundColor = background; 286 287 dialog.addEventListener('keydown', function(e) { 288 if (clickOnReturn && e.key === "Enter") { 289 e.preventDefault(); 290 button.click(); 291 } else if (clickOnEscape && e.key === "Escape") { 292 e.preventDefault(); 293 button.click(); 294 } 295 }); 296 dialog.addEventListener('cancel', function(e){ 297 e.preventDefault(); 298 }); 299 300 button.onmouseenter = function(e){ 301 button.style.backgroundColor = hovered; 302 }; 303 button.onmouseleave = function(e){ 304 button.style.backgroundColor = background; 305 }; 306 button.onclick = function(e) { 307 dialog.close(responseId); 308 }; 309 310 dialog.append(button); 311 return true; 312 }, 313 dialog_id, 314 button.text, 315 button.buttonID, 316 button.flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 317 button.flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 318 button_border, 319 button_background, 320 button_hovered 321 ); 322 323 if (!created) { 324 return false; 325 } 326 } 327 328 while (true) { 329 // give back control to browser for screen refresh 330 emscripten_sleep(0); 331 332 const int dialog_open = EM_ASM_INT({ 333 var dialog_id = UTF8ToString($0); 334 335 var dialog = document.getElementById(dialog_id); 336 if (!dialog) { 337 return false; 338 } 339 return dialog.open; 340 }, dialog_id); 341 342 if (dialog_open) { 343 continue; 344 } 345 346 *buttonID = EM_ASM_INT({ 347 var dialog_id = UTF8ToString($0); 348 var dialog = document.getElementById(dialog_id); 349 if (!dialog) { 350 return 0; 351 } 352 try 353 { 354 return parseInt(dialog.returnValue); 355 } 356 catch(e) 357 { 358 return 0; 359 } 360 }, dialog_id); 361 break; 362 } 363 364 } else { 365 // Cannot add elements to DOM and block without Asyncify. So, fall back to the alert function. 366 EM_ASM({ 367 alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1)); 368 }, messageboxdata->title, messageboxdata->message); 369 } 370 return true; 371} 372 373VideoBootStrap Emscripten_bootstrap = { 374 EMSCRIPTENVID_DRIVER_NAME, "SDL emscripten video driver", 375 Emscripten_CreateDevice, 376 Emscripten_ShowMessagebox, 377 false 378}; 379 380bool Emscripten_VideoInit(SDL_VideoDevice *_this) 381{ 382 SDL_DisplayMode mode; 383 384 // Use a fake 32-bpp desktop mode 385 SDL_zero(mode); 386 mode.format = SDL_PIXELFORMAT_RGBA32; 387 emscripten_get_screen_size(&mode.w, &mode.h); 388 mode.pixel_density = emscripten_get_device_pixel_ratio(); 389 390 if (SDL_AddBasicVideoDisplay(&mode) == 0) { 391 return false; 392 } 393 394 Emscripten_InitMouse(); 395 396 // Assume we have a mouse and keyboard 397 SDL_AddKeyboard(SDL_DEFAULT_KEYBOARD_ID, NULL); 398 SDL_AddMouse(SDL_DEFAULT_MOUSE_ID, NULL); 399 400 Emscripten_RegisterGlobalEventHandlers(_this); 401 402 // We're done! 403 return true; 404} 405 406static bool Emscripten_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode) 407{ 408 // can't do this 409 return true; 410} 411 412static void Emscripten_VideoQuit(SDL_VideoDevice *_this) 413{ 414 Emscripten_UnregisterGlobalEventHandlers(_this); 415 Emscripten_QuitMouse(); 416 Emscripten_UnlistenSystemTheme(); 417 pumpevents_has_run = false; 418 pending_swap_interval = -1; 419} 420 421static bool Emscripten_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) 422{ 423 if (rect) { 424 rect->x = 0; 425 rect->y = 0; 426 rect->w = MAIN_THREAD_EM_ASM_INT({ 427 return window.innerWidth; 428 }); 429 rect->h = MAIN_THREAD_EM_ASM_INT({ 430 return window.innerHeight; 431 }); 432 } 433 return true; 434} 435 436bool Emscripten_ShouldSetSwapInterval(int interval) 437{ 438 if (!pumpevents_has_run) { 439 pending_swap_interval = interval; 440 return false; 441 } 442 return true; 443} 444 445static void Emscripten_PumpEvents(SDL_VideoDevice *_this) 446{ 447 if (!pumpevents_has_run) { 448 // we assume you've set a mainloop by the time you've called pumpevents, so we delay initial SetInterval changes until then. 449 // otherwise you'll get a warning on the javascript console. 450 pumpevents_has_run = true; 451 if (pending_swap_interval >= 0) { 452 Emscripten_GLES_SetSwapInterval(_this, pending_swap_interval); 453 pending_swap_interval = -1; 454 } 455 } 456} 457 458EMSCRIPTEN_KEEPALIVE void requestFullscreenThroughSDL(SDL_Window *window) 459{ 460 SDL_SetWindowFullscreen(window, true); 461} 462 463static bool Emscripten_SetWindowFillDocument(SDL_VideoDevice *_this, SDL_Window *window, bool fill) 464{ 465 SDL_WindowData *wdata = window->internal; 466 467 if (fill && Emscripten_fill_document_window && (Emscripten_fill_document_window != window)) { 468 return SDL_SetError("Only one fill-document window allowed at a time."); 469 } 470 471 // fill_document takes up the entire page and resizes as the browser window resizes. 472 if (fill) { 473 Emscripten_fill_document_window = window; 474 475 const int w = MAIN_THREAD_EM_ASM_INT({ return window.innerWidth; }); 476 const int h = MAIN_THREAD_EM_ASM_INT({ return window.innerHeight; }); 477 const double scaled_w = w * wdata->pixel_ratio; 478 const double scaled_h = h * wdata->pixel_ratio; 479 480 wdata->non_fill_document_width = window->w; 481 wdata->non_fill_document_height = window->h; 482 483 MAIN_THREAD_EM_ASM({ 484 var canvas = document.querySelector(UTF8ToString($0)); 485 canvas.SDL3_original_position = canvas.style.position; 486 canvas.SDL3_original_top = canvas.style.top; 487 canvas.SDL3_original_left = canvas.style.left; 488 489 // hide everything on the page that isn't the canvas. 490 var div = document.createElement('div'); 491 div.id = 'SDL3_fill_document_background_elements'; 492 493 div.SDL3_canvas = canvas; 494 div.SDL3_canvas_parent = canvas.parentNode; 495 div.SDL3_canvas_nextsib = canvas.nextSibling; 496 497 var children = Array.from(document.body.children); 498 for (var child of children) { 499 div.appendChild(child); 500 } 501 document.body.appendChild(div); 502 div.style.display = 'none'; 503 document.body.appendChild(canvas); 504 canvas.style.position = 'fixed'; 505 canvas.style.top = '0'; 506 canvas.style.left = '0'; 507 }, wdata->canvas_id); 508 509 emscripten_set_canvas_element_size(wdata->canvas_id, SDL_lroundf(scaled_w), SDL_lroundf(scaled_h)); 510 511 // set_canvas_size unsets this 512 if (wdata->pixel_ratio != 1.0f) { 513 emscripten_set_element_css_size(wdata->canvas_id, w, h); 514 } 515 516 // force the event to trigger, so pixel ratio changes can be handled 517 window->w = window->h = 0; 518 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(w), SDL_lroundf(h)); 519 } else { 520 const bool transitioning = (Emscripten_fill_document_window == window); 521 if (transitioning) { 522 MAIN_THREAD_EM_ASM({ 523 // if we had previously hidden everything behind a fill_document window, put it back. 524 var div = document.getElementById('SDL3_fill_document_background_elements'); 525 if (div) { 526 if (div.SDL3_canvas_nextsib) { 527 div.SDL3_canvas_parent.insertBefore(div.SDL3_canvas, div.SDL3_canvas_nextsib); 528 } else { 529 div.SDL3_canvas_parent.appendChild(div.SDL3_canvas); 530 } 531 while (div.firstChild) { 532 document.body.insertBefore(div.firstChild, div); 533 } 534 div.SDL3_canvas.style.position = div.SDL3_canvas.SDL3_original_position; 535 div.SDL3_canvas.style.top = div.SDL3_canvas.SDL3_original_top; 536 div.SDL3_canvas.style.left = div.SDL3_canvas.SDL3_original_left; 537 div.remove(); 538 } 539 }); 540 Emscripten_fill_document_window = NULL; 541 } 542 543 window->w = wdata->non_fill_document_width; 544 window->h = wdata->non_fill_document_height; 545 const double scaled_w = SDL_floor(window->w * wdata->pixel_ratio); 546 const double scaled_h = SDL_floor(window->h * wdata->pixel_ratio); 547 emscripten_set_canvas_element_size(wdata->canvas_id, SDL_lroundf(scaled_w), SDL_lroundf(scaled_h)); 548 549 // if the size is not being controlled by css, we need to scale down for hidpi 550 if (!wdata->external_size && (wdata->pixel_ratio != 1.0f)) { 551 // scale canvas down 552 emscripten_set_element_css_size(wdata->canvas_id, window->w, window->h); 553 } 554 555 if (transitioning) { 556 window->w = window->h = 0; 557 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, wdata->non_fill_document_width, wdata->non_fill_document_height); 558 } 559 } 560 561 return true; 562} 563 564static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) 565{ 566 SDL_WindowData *wdata; 567 double css_w, css_h; 568 const char *selector; 569 570 bool fill_document = ((window->flags & SDL_WINDOW_FILL_DOCUMENT) != 0); 571 if (fill_document && Emscripten_fill_document_window) { 572 fill_document = false; // only one allowed at a time. 573 window->flags &= ~SDL_WINDOW_FILL_DOCUMENT; // !!! FIXME: should this fail instead? 574 } 575 576 // Allocate window internal data 577 wdata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); 578 if (!wdata) { 579 return false; 580 } 581 582 selector = SDL_GetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR); 583 if (!selector || !*selector) { 584 selector = SDL_GetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING, "#canvas"); 585 } 586 wdata->canvas_id = SDL_strdup(selector); 587 588 selector = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT); 589 if (!selector || !*selector) { 590 selector = SDL_GetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING, "#window"); 591 } 592 wdata->keyboard_element = SDL_strdup(selector); 593 594 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { 595 wdata->pixel_ratio = emscripten_get_device_pixel_ratio(); 596 } else { 597 wdata->pixel_ratio = 1.0f; 598 } 599 600 // set a fake size to check if there is any CSS sizing the canvas 601 emscripten_set_canvas_element_size(wdata->canvas_id, 1, 1); 602 emscripten_get_element_css_size(wdata->canvas_id, &css_w, &css_h); 603 604 wdata->external_size = SDL_floor(css_w) != 1 || SDL_floor(css_h) != 1; 605 if (wdata->external_size) { 606 fill_document = false; // can't be resizable if something else is controlling it. 607 } 608 609 wdata->window = window; 610 611 // Setup driver data for this window 612 window->internal = wdata; 613 614 wdata->non_fill_document_width = window->w; 615 wdata->non_fill_document_height = window->h; 616 Emscripten_SetWindowFillDocument(_this, window, fill_document); 617 618 // One window, it always has focus 619 SDL_SetMouseFocus(window); 620 SDL_SetKeyboardFocus(window); 621 622 Emscripten_RegisterEventHandlers(wdata); 623 624 // Make the emscripten "fullscreen" button go through SDL. 625 MAIN_THREAD_EM_ASM({ 626 Module['requestFullscreen'] = function(lockPointer, resizeCanvas) { 627 _requestFullscreenThroughSDL($0); 628 }; 629 }, window); 630 631 // Ensure various things are added to the window's properties 632 SDL_SetStringProperty(window->props, SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING, wdata->canvas_id); 633 SDL_SetStringProperty(window->props, SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING, wdata->keyboard_element); 634 635 // Window has been successfully created 636 return true; 637} 638 639static void Emscripten_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window * window, bool resizable) 640{ 641 // this function just has to exist or the higher level won't let the window change its SDL_WINDOW_RESIZABLE flag. 642} 643 644static void Emscripten_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) 645{ 646 if (window->internal) { 647 SDL_WindowData *data = window->internal; 648 if (window == Emscripten_fill_document_window) { 649 return; // canvas size is being dictated by the browser window size, refuse request. 650 } 651 652 // update pixel ratio 653 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { 654 data->pixel_ratio = emscripten_get_device_pixel_ratio(); 655 } 656 emscripten_set_canvas_element_size(data->canvas_id, SDL_lroundf(window->pending.w * data->pixel_ratio), SDL_lroundf(window->pending.h * data->pixel_ratio)); 657 658 // scale canvas down 659 if (!data->external_size && data->pixel_ratio != 1.0f) { 660 emscripten_set_element_css_size(data->canvas_id, window->pending.w, window->pending.h); 661 } 662 663 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->pending.w, window->pending.h); 664 } 665} 666 667static void Emscripten_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h) 668{ 669 SDL_WindowData *data; 670 if (window->internal) { 671 data = window->internal; 672 *w = SDL_lroundf(window->w * data->pixel_ratio); 673 *h = SDL_lroundf(window->h * data->pixel_ratio); 674 } 675} 676 677static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) 678{ 679 SDL_WindowData *data; 680 681 Emscripten_SetWindowFillDocument(_this, window, false); 682 683 if (window->internal) { 684 data = window->internal; 685 686 Emscripten_UnregisterEventHandlers(data); 687 688 // We can't destroy the canvas, so resize it to zero instead 689 emscripten_set_canvas_element_size(data->canvas_id, 0, 0); 690 if (data->pixel_ratio != 1.0f) { 691 emscripten_set_element_css_size(data->canvas_id, 1, 1); 692 } 693 SDL_free(data->canvas_id); 694 695 SDL_free(data->keyboard_element); 696 697 SDL_free(window->internal); 698 window->internal = NULL; 699 } 700 701 MAIN_THREAD_EM_ASM({ 702 // just ignore clicks on the fullscreen button while there's no SDL window. 703 Module['requestFullscreen'] = function(lockPointer, resizeCanvas) {}; 704 }); 705} 706 707static SDL_FullscreenResult Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen) 708{ 709 SDL_WindowData *data; 710 int res = -1; 711 712 if (window->internal) { 713 data = window->internal; 714 715 if (data->fullscreen_change_in_progress) { 716 return SDL_FULLSCREEN_FAILED;; 717 } 718 719 EmscriptenFullscreenChangeEvent fsevent; 720 if (emscripten_get_fullscreen_status(&fsevent) == EMSCRIPTEN_RESULT_SUCCESS) { 721 if ((fullscreen == SDL_FULLSCREEN_OP_ENTER) == fsevent.isFullscreen) { 722 return SDL_FULLSCREEN_SUCCEEDED; // already there. 723 } 724 } 725 726 if (fullscreen) { 727 EmscriptenFullscreenStrategy strategy; 728 bool is_fullscreen_desktop = !window->fullscreen_exclusive; 729 730 SDL_zero(strategy); 731 strategy.scaleMode = is_fullscreen_desktop ? EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH : EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT; 732 733 if (!is_fullscreen_desktop) { 734 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE; 735 } else if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { 736 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF; 737 } else { 738 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; 739 } 740 741 strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; 742 743 strategy.canvasResizedCallback = Emscripten_HandleCanvasResize; 744 strategy.canvasResizedCallbackUserData = data; 745 746 data->fullscreen_mode_flags = (window->flags & SDL_WINDOW_FULLSCREEN); 747 data->fullscreen_resize = is_fullscreen_desktop; 748 749 res = emscripten_request_fullscreen_strategy(data->canvas_id, 1, &strategy); 750 } else { 751 res = emscripten_exit_fullscreen(); 752 } 753 } 754 755 if (res == EMSCRIPTEN_RESULT_SUCCESS) { 756 data->fullscreen_change_in_progress = true; // even on success, this might animate to the new state. 757 return SDL_FULLSCREEN_SUCCEEDED; 758 } else if (res == EMSCRIPTEN_RESULT_DEFERRED) { 759 data->fullscreen_change_in_progress = true; 760 return SDL_FULLSCREEN_PENDING; 761 } else { 762 return SDL_FULLSCREEN_FAILED; 763 } 764} 765 766static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) 767{ 768 emscripten_set_window_title(window->title); 769} 770 771static bool Emscripten_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) 772{ 773 // Create dynamic memory stream for PNG encoding 774 SDL_IOStream *stream = SDL_IOFromDynamicMem(); 775 if (!stream) { 776 return false; 777 } 778 779 // Encode icon to PNG using SDL's native PNG encoder 780 if (!SDL_SavePNG_IO(icon, stream, false)) { 781 SDL_CloseIO(stream); 782 return false; 783 } 784 785 // Get the PNG data from the stream 786 void *png_data = SDL_GetPointerProperty( 787 SDL_GetIOProperties(stream), 788 SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, 789 NULL 790 ); 791 size_t png_size = (size_t)SDL_GetIOSize(stream); 792 793 // Pass PNG data to JavaScript 794 MAIN_THREAD_EM_ASM({ 795 var pngData = HEAPU8.subarray($0, $0 + $1); 796 if (pngData.buffer instanceof SharedArrayBuffer) { 797 // explicitly create a copy 798 pngData = new Uint8Array(pngData); 799 } 800 801 var blob = new Blob([pngData], {type: 'image/png'}); 802 var url = URL.createObjectURL(blob); 803 804 // Set as favicon (create link element if it doesn't exist) 805 var link = document.querySelector("link[rel~='icon']"); 806 if (!link) { 807 link = document.createElement('link'); 808 link.rel = 'icon'; 809 link.type = 'image/png'; 810 document.head.appendChild(link); 811 } 812 813 // Revoke old URL if it exists to prevent memory leaks 814 if (link.href && link.href.startsWith('blob:')) { 815 URL.revokeObjectURL(link.href); 816 } 817 818 link.href = url; 819 }, png_data, png_size); 820 821 SDL_CloseIO(stream); 822 return true; 823} 824 825#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN 826
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.