Atlas - SDL_emscriptenevents.c

Home / ext / SDL / src / video / emscripten Lines: 1 | Size: 58247 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 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 SDL_Window *Emscripten_GetFocusedWindow(SDL_VideoDevice *device) 264{ 265 SDL_Window *window; 266 for (window = device->windows; window; window = window->next) { 267 SDL_WindowData *wdata = window->internal; 268 269 const int focused = MAIN_THREAD_EM_ASM_INT({ 270 var id = UTF8ToString($0); 271 try 272 { 273 var canvas = document.querySelector(id); 274 if (canvas) { 275 return canvas === document.activeElement; 276 } 277 } 278 catch (e) 279 { 280 // querySelector throws if not a valid selector 281 } 282 return false; 283 }, wdata->canvas_id); 284 285 if (focused) { 286 break; 287 } 288 } 289 // If the DOM is focused, then at least one canvas in the DOM should be considered focused. 290 // So in this case, just assume that the first canvas is focused. 291 if (!window) { 292 const int focused = MAIN_THREAD_EM_ASM_INT({ 293 return document.hasFocus(); 294 }); 295 if (focused) { 296 window = device->windows; 297 } 298 } 299 return window; 300} 301 302static EM_BOOL Emscripten_HandlePointerLockChange(int eventType, const EmscriptenPointerlockChangeEvent *changeEvent, void *userData) 303{ 304 SDL_WindowData *window_data = (SDL_WindowData *)userData; 305 // keep track of lock losses, so we can regrab if/when appropriate. 306 window_data->has_pointer_lock = changeEvent->isActive; 307 return 0; 308} 309 310static EM_BOOL Emscripten_HandlePointerLockChangeGlobal(int eventType, const EmscriptenPointerlockChangeEvent *changeEvent, void *userData) 311{ 312 SDL_VideoDevice *device = userData; 313 bool prevent_default = false; 314 SDL_Window *window; 315 316 for (window = device->windows; window; window = window->next) { 317 prevent_default |= Emscripten_HandlePointerLockChange(eventType, changeEvent, window->internal); 318 } 319 320 return prevent_default; 321} 322 323static EM_BOOL Emscripten_HandleWheel(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData) 324{ 325 SDL_WindowData *window_data = userData; 326 327 float deltaY = wheelEvent->deltaY; 328 float deltaX = wheelEvent->deltaX; 329 330 switch (wheelEvent->deltaMode) { 331 case DOM_DELTA_PIXEL: 332 deltaX /= 100; // 100 pixels make up a step 333 deltaY /= 100; // 100 pixels make up a step 334 break; 335 case DOM_DELTA_LINE: 336 deltaX /= 3; // 3 lines make up a step 337 deltaY /= 3; // 3 lines make up a step 338 break; 339 case DOM_DELTA_PAGE: 340 deltaX *= 80; // A page makes up 80 steps 341 deltaY *= 80; // A page makes up 80 steps 342 break; 343 } 344 345 SDL_SendMouseWheel(0, window_data->window, SDL_DEFAULT_MOUSE_ID, deltaX, -deltaY, SDL_MOUSEWHEEL_NORMAL); 346 return SDL_EventEnabled(SDL_EVENT_MOUSE_WHEEL); 347} 348 349static EM_BOOL Emscripten_HandleFocus(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData) 350{ 351 SDL_VideoDevice *device = userData; 352 SDL_Window *window = Emscripten_GetFocusedWindow(device); 353 354 SDL_EventType sdl_event_type; 355 356 /* If the user switches away while keys are pressed (such as 357 * via Alt+Tab), key release events won't be received. */ 358 if (eventType == EMSCRIPTEN_EVENT_BLUR) { 359 SDL_ResetKeyboard(); 360 } 361 362 sdl_event_type = (eventType == EMSCRIPTEN_EVENT_FOCUS) ? SDL_EVENT_WINDOW_FOCUS_GAINED : SDL_EVENT_WINDOW_FOCUS_LOST; 363 SDL_SetKeyboardFocus(sdl_event_type == SDL_EVENT_WINDOW_FOCUS_GAINED ? window : NULL); 364 return SDL_EventEnabled(sdl_event_type); 365} 366 367static bool IsFunctionKey(SDL_Scancode scancode) 368{ 369 if (scancode >= SDL_SCANCODE_F1 && scancode <= SDL_SCANCODE_F12) { 370 return true; 371 } 372 if (scancode >= SDL_SCANCODE_F13 && scancode <= SDL_SCANCODE_F24) { 373 return true; 374 } 375 return false; 376} 377 378/* This is a great tool to see web keyboard events live: 379 * https://w3c.github.io/uievents/tools/key-event-viewer.html 380 */ 381static EM_BOOL Emscripten_HandleKey(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData) 382{ 383 SDL_WindowData *window_data = (SDL_WindowData *)userData; 384 SDL_Scancode scancode = Emscripten_MapScanCode(keyEvent->code); 385 SDL_Keycode keycode = SDLK_UNKNOWN; 386 bool prevent_default = false; 387 bool is_nav_key = false; 388 389 if (scancode == SDL_SCANCODE_UNKNOWN) { 390 if (SDL_strcmp(keyEvent->key, "Sleep") == 0) { 391 scancode = SDL_SCANCODE_SLEEP; 392 } else if (SDL_strcmp(keyEvent->key, "ChannelUp") == 0) { 393 scancode = SDL_SCANCODE_CHANNEL_INCREMENT; 394 } else if (SDL_strcmp(keyEvent->key, "ChannelDown") == 0) { 395 scancode = SDL_SCANCODE_CHANNEL_DECREMENT; 396 } else if (SDL_strcmp(keyEvent->key, "MediaPlay") == 0) { 397 scancode = SDL_SCANCODE_MEDIA_PLAY; 398 } else if (SDL_strcmp(keyEvent->key, "MediaPause") == 0) { 399 scancode = SDL_SCANCODE_MEDIA_PAUSE; 400 } else if (SDL_strcmp(keyEvent->key, "MediaRecord") == 0) { 401 scancode = SDL_SCANCODE_MEDIA_RECORD; 402 } else if (SDL_strcmp(keyEvent->key, "MediaFastForward") == 0) { 403 scancode = SDL_SCANCODE_MEDIA_FAST_FORWARD; 404 } else if (SDL_strcmp(keyEvent->key, "MediaRewind") == 0) { 405 scancode = SDL_SCANCODE_MEDIA_REWIND; 406 } else if (SDL_strcmp(keyEvent->key, "Close") == 0) { 407 scancode = SDL_SCANCODE_AC_CLOSE; 408 } else if (SDL_strcmp(keyEvent->key, "New") == 0) { 409 scancode = SDL_SCANCODE_AC_NEW; 410 } else if (SDL_strcmp(keyEvent->key, "Open") == 0) { 411 scancode = SDL_SCANCODE_AC_OPEN; 412 } else if (SDL_strcmp(keyEvent->key, "Print") == 0) { 413 scancode = SDL_SCANCODE_AC_PRINT; 414 } else if (SDL_strcmp(keyEvent->key, "Save") == 0) { 415 scancode = SDL_SCANCODE_AC_SAVE; 416 } else if (SDL_strcmp(keyEvent->key, "Props") == 0) { 417 scancode = SDL_SCANCODE_AC_PROPERTIES; 418 } 419 } 420 421 if (scancode == SDL_SCANCODE_UNKNOWN) { 422 // KaiOS Left Soft Key and Right Soft Key, they act as OK/Next/Menu and Cancel/Back/Clear 423 if (SDL_strcmp(keyEvent->key, "SoftLeft") == 0) { 424 scancode = SDL_SCANCODE_AC_FORWARD; 425 } else if (SDL_strcmp(keyEvent->key, "SoftRight") == 0) { 426 scancode = SDL_SCANCODE_AC_BACK; 427 } 428 } 429 430 if (keyEvent->location == 0 && SDL_utf8strlen(keyEvent->key) == 1) { 431 const char *key = keyEvent->key; 432 keycode = SDL_StepUTF8(&key, NULL); 433 if (keycode == SDL_INVALID_UNICODE_CODEPOINT) { 434 keycode = SDLK_UNKNOWN; 435 } 436 } 437 438 if (keycode != SDLK_UNKNOWN) { 439 prevent_default = SDL_SendKeyboardKeyAndKeycode(0, SDL_DEFAULT_KEYBOARD_ID, 0, scancode, keycode, (eventType == EMSCRIPTEN_EVENT_KEYDOWN)); 440 } else { 441 prevent_default = SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, 0, scancode, (eventType == EMSCRIPTEN_EVENT_KEYDOWN)); 442 } 443 444 /* if TEXTINPUT events are enabled we can't prevent keydown or we won't get keypress 445 * we need to ALWAYS prevent backspace and tab otherwise chrome takes action and does bad navigation UX 446 */ 447 if ((scancode == SDL_SCANCODE_BACKSPACE) || 448 (scancode == SDL_SCANCODE_TAB) || 449 (scancode == SDL_SCANCODE_LEFT) || 450 (scancode == SDL_SCANCODE_UP) || 451 (scancode == SDL_SCANCODE_RIGHT) || 452 (scancode == SDL_SCANCODE_DOWN) || 453 IsFunctionKey(scancode) || 454 keyEvent->ctrlKey) { 455 is_nav_key = true; 456 } 457 458 if ((eventType == EMSCRIPTEN_EVENT_KEYDOWN) && SDL_TextInputActive(window_data->window) && !is_nav_key) { 459 prevent_default = false; 460 } 461 462 return prevent_default; 463} 464 465static EM_BOOL Emscripten_HandleKeyPress(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData) 466{ 467 SDL_WindowData *window_data = (SDL_WindowData *)userData; 468 469 if (SDL_TextInputActive(window_data->window)) { 470 char text[5]; 471 char *end = SDL_UCS4ToUTF8(keyEvent->charCode, text); 472 *end = '\0'; 473 SDL_SendKeyboardText(text); 474 return EM_TRUE; 475 } 476 return EM_FALSE; 477} 478 479static EM_BOOL Emscripten_HandleFullscreenChange(int eventType, const EmscriptenFullscreenChangeEvent *fullscreenChangeEvent, void *userData) 480{ 481 SDL_WindowData *window_data = userData; 482 483 window_data->fullscreen_change_in_progress = false; 484 485 if (fullscreenChangeEvent->isFullscreen) { 486 SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); 487 window_data->fullscreen_mode_flags = 0; 488 } else { 489 SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); 490 } 491 492 SDL_UpdateFullscreenMode(window_data->window, fullscreenChangeEvent->isFullscreen, false); 493 494 return 0; 495} 496 497static EM_BOOL Emscripten_HandleFullscreenChangeGlobal(int eventType, const EmscriptenFullscreenChangeEvent *fullscreenChangeEvent, void *userData) 498{ 499 SDL_VideoDevice *device = userData; 500 SDL_Window *window = NULL; 501 for (window = device->windows; window != NULL; window = window->next) { 502 const char *canvas_id = window->internal->canvas_id; 503 if (*canvas_id == '#') { 504 canvas_id++; 505 } 506 if (SDL_strcmp(fullscreenChangeEvent->id, canvas_id) == 0) { 507 break; // this is the window. 508 } 509 } 510 511 if (window) { 512 return Emscripten_HandleFullscreenChange(eventType, fullscreenChangeEvent, window->internal); 513 } 514 515 return EM_FALSE; 516} 517 518static EM_BOOL Emscripten_HandleResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) 519{ 520 SDL_WindowData *window_data = userData; 521 522 if (!(window_data->window->flags & SDL_WINDOW_FULLSCREEN)) { 523 bool force = false; 524 525 // update pixel ratio 526 if (window_data->window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { 527 if (window_data->pixel_ratio != emscripten_get_device_pixel_ratio()) { 528 window_data->pixel_ratio = emscripten_get_device_pixel_ratio(); 529 force = true; 530 } 531 } 532 const bool fill_document = (Emscripten_fill_document_window == window_data->window); 533 if (fill_document || (window_data->window->flags & SDL_WINDOW_RESIZABLE)) { 534 double w, h; 535 if (fill_document) { 536 w = (double) uiEvent->windowInnerWidth; 537 h = (double) uiEvent->windowInnerHeight; 538 } else { 539 SDL_assert(window_data->window->flags & SDL_WINDOW_RESIZABLE); 540 w = window_data->window->w; 541 h = window_data->window->h; 542 // this will only work if the canvas size is set through css 543 if (window_data->external_size) { 544 emscripten_get_element_css_size(window_data->canvas_id, &w, &h); 545 } 546 } 547 548 emscripten_set_canvas_element_size(window_data->canvas_id, SDL_lroundf(w * window_data->pixel_ratio), SDL_lroundf(h * window_data->pixel_ratio)); 549 550 // set_canvas_size unsets this 551 if (!window_data->external_size && window_data->pixel_ratio != 1.0f) { 552 emscripten_set_element_css_size(window_data->canvas_id, w, h); 553 } 554 555 if (force) { 556 // force the event to trigger, so pixel ratio changes can be handled 557 window_data->window->w = 0; 558 window_data->window->h = 0; 559 } 560 561 SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(w), SDL_lroundf(h)); 562 } 563 } 564 565 return 0; 566} 567 568static EM_BOOL Emscripten_HandleResizeGlobal(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) 569{ 570 SDL_VideoDevice *device = userData; 571 bool prevent_default = false; 572 SDL_Window *window; 573 574 for (window = device->windows; window; window = window->next) { 575 prevent_default |= Emscripten_HandleResize(eventType, uiEvent, window->internal); 576 } 577 578 return prevent_default; 579} 580 581EM_BOOL 582Emscripten_HandleCanvasResize(int eventType, const void *reserved, void *userData) 583{ 584 // this is used during fullscreen changes 585 SDL_WindowData *window_data = userData; 586 587 if (window_data->fullscreen_resize) { 588 double css_w, css_h; 589 emscripten_get_element_css_size(window_data->canvas_id, &css_w, &css_h); 590 SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(css_w), SDL_lroundf(css_h)); 591 } 592 593 return 0; 594} 595 596static EM_BOOL Emscripten_HandleVisibilityChange(int eventType, const EmscriptenVisibilityChangeEvent *visEvent, void *userData) 597{ 598 SDL_WindowData *window_data = userData; 599 SDL_SendWindowEvent(window_data->window, visEvent->hidden ? SDL_EVENT_WINDOW_HIDDEN : SDL_EVENT_WINDOW_SHOWN, 0, 0); 600 return 0; 601} 602 603static const char *Emscripten_HandleBeforeUnload(int eventType, const void *reserved, void *userData) 604{ 605 /* This event will need to be handled synchronously, e.g. using 606 SDL_AddEventWatch, as the page is being closed *now*. */ 607 // No need to send a SDL_EVENT_QUIT, the app won't get control again. 608 SDL_SendAppEvent(SDL_EVENT_TERMINATING); 609 return ""; // don't trigger confirmation dialog 610} 611 612static EM_BOOL Emscripten_HandleOrientationChange(int eventType, const EmscriptenOrientationChangeEvent *orientationChangeEvent, void *userData) 613{ 614 SDL_DisplayOrientation orientation; 615 switch (orientationChangeEvent->orientationIndex) { 616 #define CHECK_ORIENTATION(emsdk, sdl) case EMSCRIPTEN_ORIENTATION_##emsdk: orientation = SDL_ORIENTATION_##sdl; break 617 CHECK_ORIENTATION(LANDSCAPE_PRIMARY, LANDSCAPE); 618 CHECK_ORIENTATION(LANDSCAPE_SECONDARY, LANDSCAPE_FLIPPED); 619 CHECK_ORIENTATION(PORTRAIT_PRIMARY, PORTRAIT); 620 CHECK_ORIENTATION(PORTRAIT_SECONDARY, PORTRAIT_FLIPPED); 621 #undef CHECK_ORIENTATION 622 default: orientation = SDL_ORIENTATION_UNKNOWN; break; 623 } 624 625 SDL_WindowData *window_data = (SDL_WindowData *) userData; 626 SDL_SendDisplayEvent(SDL_GetVideoDisplayForWindow(window_data->window), SDL_EVENT_DISPLAY_ORIENTATION, orientation, 0); 627 628 // fake a UI event so we can tell the app the canvas might have resized. 629 EmscriptenUiEvent uiEvent; 630 SDL_zero(uiEvent); 631 uiEvent.documentBodyClientWidth = MAIN_THREAD_EM_ASM_INT( { return document.body.clientWidth; } ); 632 uiEvent.documentBodyClientHeight = MAIN_THREAD_EM_ASM_INT( { return document.body.clientHeight; } ); 633 uiEvent.windowInnerWidth = MAIN_THREAD_EM_ASM_INT( { return window.innerWidth; } ); 634 uiEvent.windowInnerHeight = MAIN_THREAD_EM_ASM_INT( { return window.innerHeight; } ); 635 uiEvent.windowOuterWidth = MAIN_THREAD_EM_ASM_INT( { return window.outerWidth; } ); 636 uiEvent.windowOuterHeight = MAIN_THREAD_EM_ASM_INT( { return window.outerHeight; } ); 637 uiEvent.scrollTop = MAIN_THREAD_EM_ASM_INT( { return window.pageXOffset; } ); 638 uiEvent.scrollLeft = MAIN_THREAD_EM_ASM_INT( { return window.pageYOffset; } ); 639 Emscripten_HandleResize(EMSCRIPTEN_EVENT_RESIZE, &uiEvent, userData); 640 641 return 0; 642} 643 644// IF YOU CHANGE THIS STRUCTURE, YOU NEED TO UPDATE THE JAVASCRIPT THAT FILLS IT IN: SDL3.makePointerEventCStruct, below. 645#define PTRTYPE_MOUSE 1 646#define PTRTYPE_TOUCH 2 647#define PTRTYPE_PEN 3 648typedef struct Emscripten_PointerEvent 649{ 650 int pointer_type; 651 int pointerid; 652 int button; 653 int buttons; 654 float movementX; 655 float movementY; 656 float targetX; 657 float targetY; 658 float pressure; 659 float tangential_pressure; 660 float tiltx; 661 float tilty; 662 float rotation; 663} Emscripten_PointerEvent; 664 665static void Emscripten_HandleMouseButton(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 666{ 667 Uint8 sdl_button; 668 bool down; 669 switch (event->button) { 670 #define CHECK_MOUSE_BUTTON(jsbutton, downflag, sdlbutton) case jsbutton: down = (event->buttons & downflag) != 0; ; sdl_button = SDL_BUTTON_##sdlbutton; break 671 CHECK_MOUSE_BUTTON(0, 1, LEFT); 672 CHECK_MOUSE_BUTTON(1, 4, MIDDLE); 673 CHECK_MOUSE_BUTTON(2, 2, RIGHT); 674 CHECK_MOUSE_BUTTON(3, 8, X1); 675 CHECK_MOUSE_BUTTON(4, 16, X2); 676 #undef CHECK_MOUSE_BUTTON 677 default: sdl_button = 0; break; 678 } 679 680 if (sdl_button) { 681 const SDL_Mouse *mouse = SDL_GetMouse(); 682 SDL_assert(mouse != NULL); 683 684 if (down) { 685 if (mouse->relative_mode && !window_data->has_pointer_lock) { 686 emscripten_request_pointerlock(window_data->canvas_id, 0); // try to regrab lost pointer lock. 687 } 688 } 689 690 SDL_SendMouseButton(0, window_data->window, SDL_DEFAULT_MOUSE_ID, sdl_button, down); 691 692 // We have an imaginary mouse capture, because we need SDL to not drop our imaginary mouse focus when we leave the canvas. 693 if (mouse->auto_capture) { 694 if (SDL_GetMouseState(NULL, NULL) != 0) { 695 window_data->window->flags |= SDL_WINDOW_MOUSE_CAPTURE; 696 } else { 697 window_data->window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE; 698 } 699 } 700 701 if (!down && window_data->mouse_focus_loss_pending) { 702 window_data->mouse_focus_loss_pending = (window_data->window->flags & SDL_WINDOW_MOUSE_CAPTURE) != 0; 703 if (!window_data->mouse_focus_loss_pending) { 704 SDL_SetMouseFocus(NULL); 705 } 706 } 707 } 708} 709 710static void Emscripten_UpdateMouseFromEvent(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 711{ 712 SDL_assert(event->pointer_type == PTRTYPE_MOUSE); 713 714 // rescale (in case canvas is being scaled) 715 double client_w, client_h; 716 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h); 717 const double xscale = window_data->window->w / client_w; 718 const double yscale = window_data->window->h / client_h; 719 720 const bool isPointerLocked = window_data->has_pointer_lock; 721 float mx, my; 722 if (isPointerLocked) { 723 mx = (float)(event->movementX * xscale); 724 my = (float)(event->movementY * yscale); 725 } else { 726 mx = (float)(event->targetX * xscale); 727 my = (float)(event->targetY * yscale); 728 } 729 730 SDL_SendMouseMotion(0, window_data->window, SDL_DEFAULT_MOUSE_ID, isPointerLocked, mx, my); 731 732 Emscripten_HandleMouseButton(window_data, event); 733} 734 735static void Emscripten_UpdateTouchFromEvent(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 736{ 737 SDL_assert(event->pointer_type == PTRTYPE_TOUCH); 738 739 const SDL_TouchID deviceId = 1; 740 if (SDL_AddTouch(deviceId, SDL_TOUCH_DEVICE_DIRECT, "") < 0) { 741 return; 742 } 743 744 double client_w, client_h; 745 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h); 746 747 const SDL_FingerID id = event->pointerid + 1; 748 float x, y; 749 if (client_w <= 1) { 750 x = 0.5f; 751 } else { 752 x = event->targetX / (client_w - 1); 753 } 754 if (client_h <= 1) { 755 y = 0.5f; 756 } else { 757 y = event->targetY / (client_h - 1); 758 } 759 760 const bool down = (event->buttons & 1) != 0; 761 if (event->button == 0) { // touch is starting or ending if this is zero (-1 means "no change"). 762 if (down) { 763 SDL_SendTouch(0, deviceId, id, window_data->window, SDL_EVENT_FINGER_DOWN, x, y, 1.0f); 764 } 765 } 766 767 SDL_SendTouchMotion(0, deviceId, id, window_data->window, x, y, 1.0f); 768 769 if (event->button == 0) { // touch is starting or ending if this is zero (-1 means "no change"). 770 if (!down) { 771 SDL_SendTouch(0, deviceId, id, window_data->window, SDL_EVENT_FINGER_UP, x, y, 1.0f); 772 } 773 } 774} 775 776static void Emscripten_UpdatePenFromEvent(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 777{ 778 SDL_assert(event->pointer_type == PTRTYPE_PEN); 779 const SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) 1); // something > 0 for the single pen handle. 780 if (pen) { 781 // rescale (in case canvas is being scaled) 782 double client_w, client_h; 783 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h); 784 const double xscale = window_data->window->w / client_w; 785 const double yscale = window_data->window->h / client_h; 786 787 const bool isPointerLocked = window_data->has_pointer_lock; 788 float mx, my; 789 if (isPointerLocked) { 790 mx = (float)(event->movementX * xscale); 791 my = (float)(event->movementY * yscale); 792 } else { 793 mx = (float)(event->targetX * xscale); 794 my = (float)(event->targetY * yscale); 795 } 796 797 SDL_SendPenMotion(0, pen, window_data->window, mx, my); 798 799 if (event->button == 0) { // pen touch 800 bool down = ((event->buttons & 1) != 0); 801 SDL_SendPenTouch(0, pen, window_data->window, false, down); 802 } else if (event->button == 5) { // eraser touch...? Not sure if this is right... 803 bool down = ((event->buttons & 32) != 0); 804 SDL_SendPenTouch(0, pen, window_data->window, true, down); 805 } else if (event->button == 1) { 806 bool down = ((event->buttons & 4) != 0); 807 SDL_SendPenButton(0, pen, window_data->window, 2, down); 808 } else if (event->button == 2) { 809 bool down = ((event->buttons & 2) != 0); 810 SDL_SendPenButton(0, pen, window_data->window, 1, down); 811 } 812 813 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_PRESSURE, event->pressure); 814 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_TANGENTIAL_PRESSURE, event->tangential_pressure); 815 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_XTILT, event->tiltx); 816 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_YTILT, event->tilty); 817 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_ROTATION, event->rotation); 818 } 819} 820 821static void Emscripten_UpdatePointerFromEvent(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 822{ 823 SDL_assert(event != NULL); 824 if (event->pointer_type == PTRTYPE_MOUSE) { 825 Emscripten_UpdateMouseFromEvent(window_data, event); 826 } else if (event->pointer_type == PTRTYPE_TOUCH) { 827 Emscripten_UpdateTouchFromEvent(window_data, event); 828 } else if (event->pointer_type == PTRTYPE_PEN) { 829 Emscripten_UpdatePenFromEvent(window_data, event); 830 } else { 831 SDL_assert(!"Unexpected pointer event type"); 832 } 833} 834 835static void Emscripten_HandleMouseFocus(SDL_WindowData *window_data, const Emscripten_PointerEvent *event, bool isenter) 836{ 837 SDL_assert(event->pointer_type == PTRTYPE_MOUSE); 838 839 const bool isPointerLocked = window_data->has_pointer_lock; 840 841 if (!isPointerLocked) { 842 // rescale (in case canvas is being scaled) 843 float mx, my; 844 double client_w, client_h; 845 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h); 846 847 mx = (float)(event->targetX * (window_data->window->w / client_w)); 848 my = (float)(event->targetY * (window_data->window->h / client_h)); 849 SDL_SendMouseMotion(0, window_data->window, SDL_GLOBAL_MOUSE_ID, isPointerLocked, mx, my); 850 } 851 852 if (isenter && window_data->mouse_focus_loss_pending) { 853 window_data->mouse_focus_loss_pending = false; // just drop the state, but don't send the enter event. 854 } else if (!isenter && (window_data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { 855 window_data->mouse_focus_loss_pending = true; // waiting on a mouse button to let go before we send the mouse focus update. 856 } else { 857 SDL_SetMouseFocus(isenter ? window_data->window : NULL); 858 } 859} 860 861static void Emscripten_HandlePenEnter(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 862{ 863 SDL_assert(event->pointer_type == PTRTYPE_PEN); 864 865 // event->pointerid is one continuous interaction; it doesn't necessarily track a specific tool over time, like the same finger's ID changed on each new touch event. 866 // as such, we only expose a single pen, and when the touch ends, we say it lost proximity instead of the calling SDL_RemovePenDevice(). 867 868 SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) 1); // something > 0 for the single pen handle. 869 if (pen) { 870 SDL_SendPenProximity(0, pen, window_data->window, true); 871 } else { 872 // Web browsers offer almost none of this information as specifics, but can without warning offer any of these specific things. 873 SDL_PenInfo peninfo; 874 SDL_zero(peninfo); 875 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; 876 peninfo.max_tilt = 90.0f; 877 peninfo.num_buttons = 2; 878 peninfo.subtype = SDL_PEN_TYPE_PEN; 879 SDL_AddPenDevice(0, NULL, window_data->window, &peninfo, (void *) (size_t) 1, true); 880 } 881 882 Emscripten_UpdatePenFromEvent(window_data, event); 883} 884 885EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerEnter(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 886{ 887 SDL_assert(event != NULL); 888 if (event->pointer_type == PTRTYPE_MOUSE) { 889 Emscripten_HandleMouseFocus(window_data, event, true); 890 } else if (event->pointer_type == PTRTYPE_PEN) { 891 Emscripten_HandlePenEnter(window_data, event); 892 } else if (event->pointer_type == PTRTYPE_TOUCH) { 893 // do nothing. 894 } else { 895 SDL_assert(!"Unexpected pointer event type"); 896 } 897} 898 899static void Emscripten_HandlePenLeave(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 900{ 901 const SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) 1); // something > 0 for the single pen handle. 902 if (pen) { 903 Emscripten_UpdatePointerFromEvent(window_data, event); // last data updates? 904 SDL_SendPenProximity(0, pen, window_data->window, false); 905 } 906} 907 908static void Emscripten_HandleTouchCancel(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 909{ 910 SDL_assert(event->pointer_type == PTRTYPE_TOUCH); 911 912 const SDL_TouchID deviceId = 1; 913 if (SDL_AddTouch(deviceId, SDL_TOUCH_DEVICE_DIRECT, "") < 0) { 914 return; 915 } 916 917 double client_w, client_h; 918 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h); 919 920 const SDL_FingerID id = event->pointerid + 1; 921 float x, y; 922 if (client_w <= 1) { 923 x = 0.5f; 924 } else { 925 x = event->targetX / (client_w - 1); 926 } 927 if (client_h <= 1) { 928 y = 0.5f; 929 } else { 930 y = event->targetY / (client_h - 1); 931 } 932 933 SDL_SendTouch(0, deviceId, id, window_data->window, SDL_EVENT_FINGER_CANCELED, x, y, 1.0f); 934} 935 936EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerLeave(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 937{ 938 SDL_assert(event != NULL); 939 if (event->pointer_type == PTRTYPE_MOUSE) { 940 Emscripten_HandleMouseFocus(window_data, event, false); 941 } else if (event->pointer_type == PTRTYPE_PEN) { 942 Emscripten_HandlePenLeave(window_data, event); 943 } else if (event->pointer_type == PTRTYPE_TOUCH) { 944 Emscripten_HandleTouchCancel(window_data, event); 945 } else { 946 SDL_assert(!"Unexpected pointer event type"); 947 } 948} 949 950EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerGeneric(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 951{ 952 SDL_assert(event != NULL); 953 Emscripten_UpdatePointerFromEvent(window_data, event); 954} 955 956static void Emscripten_prep_pointer_event_callbacks(void) 957{ 958 MAIN_THREAD_EM_ASM({ 959 if (typeof(Module['SDL3']) === 'undefined') { 960 Module['SDL3'] = {}; 961 } 962 var SDL3 = Module['SDL3']; 963 964 if (SDL3.makePointerEventCStruct === undefined) { 965 SDL3.makePointerEventCStruct = function(left, top, event) { 966 var ptrtype = 0; 967 if (event.pointerType == "mouse") { 968 ptrtype = 1; 969 } else if (event.pointerType == "touch") { 970 ptrtype = 2; 971 } else if (event.pointerType == "pen") { 972 ptrtype = 3; 973 } else { 974 return 0; 975 } 976 977 var ptr = _SDL_malloc($0); 978 if (ptr != 0) { 979 #ifdef __wasm32__ 980 var idx = ptr >> 2; 981 #elif __wasm64__ 982 var idx = Number(ptr / 4n); 983 #endif 984 HEAP32[idx++] = ptrtype; 985 HEAP32[idx++] = event.pointerId; 986 HEAP32[idx++] = (typeof(event.button) !== "undefined") ? event.button : -1; 987 HEAP32[idx++] = event.buttons; 988 HEAPF32[idx++] = event.movementX; 989 HEAPF32[idx++] = event.movementY; 990 HEAPF32[idx++] = event.clientX - left; 991 HEAPF32[idx++] = event.clientY - top; 992 if (ptrtype == 3) { 993 HEAPF32[idx++] = event.pressure; 994 HEAPF32[idx++] = event.tangentialPressure; 995 HEAPF32[idx++] = event.tiltX; 996 HEAPF32[idx++] = event.tiltY; 997 HEAPF32[idx++] = event.twist; 998 } 999 } 1000 return ptr; 1001 }; 1002 } 1003 }, sizeof (Emscripten_PointerEvent)); 1004} 1005 1006static void Emscripten_set_pointer_event_callbacks(SDL_WindowData *data) 1007{ 1008 Emscripten_prep_pointer_event_callbacks(); 1009 1010 MAIN_THREAD_EM_ASM({ 1011 var target = document.querySelector(UTF8ToString($1)); 1012 if (target) { 1013 var SDL3 = Module['SDL3']; 1014 var data = $0; 1015 target.sdlEventHandlerPointerEnter = function(event) { 1016 var rect = target.getBoundingClientRect(); 1017 var d = SDL3.makePointerEventCStruct(rect.left, rect.top, event); 1018 if (d != 0) 1019 { 1020 #ifdef __wasm32__ 1021 _Emscripten_HandlePointerEnter(data, d); 1022 #elif __wasm64__ 1023 _Emscripten_HandlePointerEnter(BigInt(data), d); 1024 #endif 1025 _SDL_free(d); 1026 } 1027 }; 1028 target.sdlEventHandlerPointerLeave = function(event) { 1029 var rect = target.getBoundingClientRect(); 1030 var d = SDL3.makePointerEventCStruct(rect.left, rect.top, event); 1031 if (d != 0) 1032 { 1033 #ifdef __wasm32__ 1034 _Emscripten_HandlePointerLeave(data, d); 1035 #elif __wasm64__ 1036 _Emscripten_HandlePointerLeave(BigInt(data), d); 1037 #endif 1038 _SDL_free(d); 1039 } 1040 }; 1041 target.sdlEventHandlerPointerGeneric = function(event) { 1042 var rect = target.getBoundingClientRect(); 1043 var d = SDL3.makePointerEventCStruct(rect.left, rect.top, event); 1044 if (d != 0) 1045 { 1046 #ifdef __wasm32__ 1047 _Emscripten_HandlePointerGeneric(data, d); 1048 #elif __wasm64__ 1049 _Emscripten_HandlePointerGeneric(BigInt(data), d); 1050 #endif 1051 _SDL_free(d); 1052 } 1053 }; 1054 1055 target.style.touchAction = "none"; // or mobile devices will scroll as your touch moves across the element. 1056 target.addEventListener("pointerenter", target.sdlEventHandlerPointerEnter); 1057 target.addEventListener("pointerleave", target.sdlEventHandlerPointerLeave); 1058 target.addEventListener("pointercancel", target.sdlEventHandlerPointerLeave); 1059 target.addEventListener("pointerdown", target.sdlEventHandlerPointerGeneric); 1060 target.addEventListener("pointermove", target.sdlEventHandlerPointerGeneric); 1061 target.addEventListener("pointerup", target.sdlEventHandlerPointerGeneric); 1062 } 1063 }, data, data->canvas_id); 1064} 1065 1066static void Emscripten_unset_pointer_event_callbacks(SDL_WindowData *data) 1067{ 1068 MAIN_THREAD_EM_ASM({ 1069 var target = document.querySelector(UTF8ToString($0)); 1070 if (target) { 1071 target.removeEventListener("pointerenter", target.sdlEventHandlerPointerEnter); 1072 target.removeEventListener("pointerleave", target.sdlEventHandlerPointerLeave); 1073 target.removeEventListener("pointercancel", target.sdlEventHandlerPointerLeave); 1074 target.removeEventListener("pointerdown", target.sdlEventHandlerPointerGeneric); 1075 target.removeEventListener("pointermove", target.sdlEventHandlerPointerGeneric); 1076 target.removeEventListener("pointerup", target.sdlEventHandlerPointerGeneric); 1077 target.style.touchAction = ""; // let mobile devices scroll again as your touch moves across the element. 1078 target.sdlEventHandlerPointerEnter = undefined; 1079 target.sdlEventHandlerPointerLeave = undefined; 1080 target.sdlEventHandlerPointerGeneric = undefined; 1081 } 1082 }, data->canvas_id); 1083} 1084 1085EMSCRIPTEN_KEEPALIVE void Emscripten_HandleMouseButtonUpGlobal(SDL_VideoDevice *device, const Emscripten_PointerEvent *event) 1086{ 1087 SDL_assert(device != NULL); 1088 SDL_assert(event != NULL); 1089 if (event->pointer_type == PTRTYPE_MOUSE) { 1090 for (SDL_Window *window = device->windows; window; window = window->next) { 1091 Emscripten_HandleMouseButton(window->internal, event); 1092 } 1093 } 1094} 1095 1096static void Emscripten_set_global_mouseup_callback(SDL_VideoDevice *device) 1097{ 1098 Emscripten_prep_pointer_event_callbacks(); 1099 1100 MAIN_THREAD_EM_ASM({ 1101 var target = document; 1102 if (target) { 1103 target.sdlEventHandlerMouseButtonUpGlobal = function(event) { 1104 var SDL3 = Module['SDL3']; 1105 var d = SDL3.makePointerEventCStruct(0, 0, event); 1106 if (d != 0) 1107 { 1108 #ifdef __wasm32__ 1109 _Emscripten_HandleMouseButtonUpGlobal($0, d); 1110 #elif __wasm64__ 1111 _Emscripten_HandleMouseButtonUpGlobal(BigInt($0), d); 1112 #endif 1113 _SDL_free(d); 1114 } 1115 }; 1116 target.addEventListener("pointerup", target.sdlEventHandlerMouseButtonUpGlobal); 1117 } 1118 }, device); 1119} 1120 1121static void Emscripten_unset_global_mouseup_callback(SDL_VideoDevice *device) 1122{ 1123 MAIN_THREAD_EM_ASM({ 1124 var target = document; 1125 if (target) { 1126 target.removeEventListener("pointerup", target.sdlEventHandlerMouseButtonUpGlobal); 1127 target.sdlEventHandlerMouseButtonUpGlobal = undefined; 1128 } 1129 }); 1130} 1131 1132// IF YOU CHANGE THIS STRUCTURE, YOU NEED TO UPDATE THE JAVASCRIPT THAT FILLS IT IN: makeDropEventCStruct, below. 1133typedef struct Emscripten_DropEvent 1134{ 1135 int x; 1136 int y; 1137} Emscripten_DropEvent; 1138 1139EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragEvent(SDL_WindowData *window_data, const Emscripten_DropEvent *event) 1140{ 1141 SDL_SendDropPosition(window_data->window, event->x, event->y); 1142} 1143 1144EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragCompleteEvent(SDL_WindowData *window_data) 1145{ 1146 SDL_SendDropComplete(window_data->window); 1147} 1148 1149EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragTextEvent(SDL_WindowData *window_data, char *text) 1150{ 1151 SDL_SendDropText(window_data->window, text); 1152} 1153 1154EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragFileEvent(SDL_WindowData *window_data, char *filename) 1155{ 1156 SDL_SendDropFile(window_data->window, NULL, filename); 1157} 1158 1159EM_JS_DEPS(dragndrop, "$writeArrayToMemory"); 1160 1161static void Emscripten_set_drag_event_callbacks(SDL_WindowData *data) 1162{ 1163 MAIN_THREAD_EM_ASM({ 1164 var target = document.querySelector(UTF8ToString($1)); 1165 if (target) { 1166 var data = $0; 1167 1168 if (typeof(Module['SDL3']) === 'undefined') { 1169 Module['SDL3'] = {}; 1170 } 1171 var SDL3 = Module['SDL3']; 1172 1173 var makeDropEventCStruct = function(event) { 1174 var ptr = 0; 1175 ptr = _SDL_malloc($2); 1176 if (ptr != 0) { 1177 var idx = ptr >> 2; 1178 var rect = target.getBoundingClientRect(); 1179 HEAP32[idx++] = event.clientX - rect.left; 1180 HEAP32[idx++] = event.clientY - rect.top; 1181 } 1182 return ptr; 1183 }; 1184 1185 SDL3.eventHandlerDropDragover = function(event) { 1186 event.preventDefault(); 1187 var d = makeDropEventCStruct(event); if (d != 0) { _Emscripten_SendDragEvent(data, d); _SDL_free(d); } 1188 }; 1189 target.addEventListener("dragover", SDL3.eventHandlerDropDragover); 1190 1191 SDL3.drop_count = 0; 1192 FS.mkdir("/tmp/filedrop"); 1193 SDL3.eventHandlerDropDrop = function(event) { 1194 event.preventDefault(); 1195 if (event.dataTransfer.types.includes("text/plain")) { 1196 let plain_text = stringToNewUTF8(event.dataTransfer.getData("text/plain")); 1197 _Emscripten_SendDragTextEvent(data, plain_text); 1198 _free(plain_text); 1199 } else if (event.dataTransfer.types.includes("Files")) { 1200 let files_read = 0; 1201 const files_to_read = event.dataTransfer.files.length; 1202 for (let i = 0; i < files_to_read; i++) { 1203 const file = event.dataTransfer.files.item(i); 1204 const file_reader = new FileReader(); 1205 file_reader.readAsArrayBuffer(file); 1206 file_reader.onload = function(event) { 1207 const fs_dropdir = `/tmp/filedrop/${SDL3.drop_count}`; 1208 SDL3.drop_count += 1; 1209 1210 const fs_filepath = `${fs_dropdir}/${file.name}`; 1211 const c_fs_filepath = stringToNewUTF8(fs_filepath); 1212 const contents_array8 = new Uint8Array(event.target.result); 1213 1214 FS.mkdir(fs_dropdir); 1215 var stream = FS.open(fs_filepath, "w"); 1216 FS.write(stream, contents_array8, 0, contents_array8.length, 0); 1217 FS.close(stream); 1218 1219 _Emscripten_SendDragFileEvent(data, c_fs_filepath); 1220 _free(c_fs_filepath); 1221 onFileRead(); 1222 }; 1223 file_reader.onerror = function(event) { 1224 // Handle when error occurs to ensure that the drag event can still complete 1225 onFileRead(); 1226 }; 1227 } 1228 function onFileRead() { 1229 ++files_read; 1230 if (files_read === files_to_read) { 1231 _Emscripten_SendDragCompleteEvent(data); 1232 } 1233 } 1234 } 1235 _Emscripten_SendDragCompleteEvent(data); 1236 }; 1237 target.addEventListener("drop", SDL3.eventHandlerDropDrop); 1238 1239 SDL3.eventHandlerDropDragend = function(event) { 1240 event.preventDefault(); 1241 _Emscripten_SendDragCompleteEvent(data); 1242 }; 1243 target.addEventListener("dragend", SDL3.eventHandlerDropDragend); 1244 target.addEventListener("dragleave", SDL3.eventHandlerDropDragend); 1245 } 1246 }, data, data->canvas_id, sizeof (Emscripten_DropEvent)); 1247} 1248 1249static void Emscripten_unset_drag_event_callbacks(SDL_WindowData *data) 1250{ 1251 MAIN_THREAD_EM_ASM({ 1252 var target = document.querySelector(UTF8ToString($0)); 1253 if (target) { 1254 var SDL3 = Module['SDL3']; 1255 target.removeEventListener("dragleave", SDL3.eventHandlerDropDragend); 1256 target.removeEventListener("dragend", SDL3.eventHandlerDropDragend); 1257 target.removeEventListener("drop", SDL3.eventHandlerDropDrop); 1258 SDL3.drop_count = undefined; 1259 1260 function recursive_remove(dirpath) { 1261 FS.readdir(dirpath).forEach((filename) => { 1262 const p = `${dirpath}/${filename}`; 1263 const p_s = FS.stat(p); 1264 if (FS.isFile(p_s.mode)) { 1265 FS.unlink(p); 1266 } else if (FS.isDir(p)) { 1267 recursive_remove(p); 1268 } 1269 }); 1270 FS.rmdir(dirpath); 1271 }("/tmp/filedrop"); 1272 1273 FS.rmdir("/tmp/filedrop"); 1274 target.removeEventListener("dragover", SDL3.eventHandlerDropDragover); 1275 SDL3.eventHandlerDropDragover = undefined; 1276 SDL3.eventHandlerDropDrop = undefined; 1277 SDL3.eventHandlerDropDragend = undefined; 1278 } 1279 }, data->canvas_id); 1280} 1281 1282static const char *Emscripten_GetKeyboardTargetElement(const char *target) 1283{ 1284 if (SDL_strcmp(target, "#none") == 0) { 1285 return NULL; 1286 } else if (SDL_strcmp(target, "#window") == 0) { 1287 return EMSCRIPTEN_EVENT_TARGET_WINDOW; 1288 } else if (SDL_strcmp(target, "#document") == 0) { 1289 return EMSCRIPTEN_EVENT_TARGET_DOCUMENT; 1290 } else if (SDL_strcmp(target, "#screen") == 0) { 1291 return EMSCRIPTEN_EVENT_TARGET_SCREEN; 1292 } 1293 1294 return target; 1295} 1296 1297void Emscripten_RegisterGlobalEventHandlers(SDL_VideoDevice *device) 1298{ 1299 Emscripten_set_global_mouseup_callback(device); 1300 1301 emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, device, 0, Emscripten_HandleFocus); 1302 emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, device, 0, Emscripten_HandleFocus); 1303 1304 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, device, 0, Emscripten_HandlePointerLockChangeGlobal); 1305 1306 emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, device, 0, Emscripten_HandleFullscreenChangeGlobal); 1307 1308 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, device, 0, Emscripten_HandleResizeGlobal); 1309} 1310 1311void Emscripten_UnregisterGlobalEventHandlers(SDL_VideoDevice *device) 1312{ 1313 Emscripten_unset_global_mouseup_callback(device); 1314 1315 emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL); 1316 emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL); 1317 1318 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL); 1319 1320 emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL); 1321 1322 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL); 1323} 1324 1325EMSCRIPTEN_KEEPALIVE void Emscripten_HandleLockKeysCheck(SDL_WindowData *window_data, EM_BOOL capslock, EM_BOOL numlock, EM_BOOL scrolllock) 1326{ 1327 const SDL_Keymod new_mods = (capslock ? SDL_KMOD_CAPS : 0) | (numlock ? SDL_KMOD_NUM : 0) | (scrolllock ? SDL_KMOD_SCROLL : 0); 1328 SDL_Keymod modstate = SDL_GetModState(); 1329 if ((modstate & (SDL_KMOD_CAPS|SDL_KMOD_NUM|SDL_KMOD_SCROLL)) != new_mods) { 1330 modstate &= ~(SDL_KMOD_CAPS|SDL_KMOD_NUM|SDL_KMOD_SCROLL); 1331 modstate |= new_mods; 1332 SDL_SetModState(modstate); 1333 } 1334} 1335 1336void Emscripten_RegisterEventHandlers(SDL_WindowData *data) 1337{ 1338 const char *keyElement; 1339 1340 // There is only one window and that window is the canvas 1341 emscripten_set_wheel_callback(data->canvas_id, data, 0, Emscripten_HandleWheel); 1342 1343 emscripten_set_orientationchange_callback(data, 0, Emscripten_HandleOrientationChange); 1344 1345 keyElement = Emscripten_GetKeyboardTargetElement(data->keyboard_element); 1346 if (keyElement) { 1347 MAIN_THREAD_EM_ASM_INT({ 1348 var data = $0; 1349 // our keymod state can get confused in various ways (changed capslock when browser didn't have focus, etc), and you can't query the current 1350 // state from the DOM, outside of a keyboard event, so catch keypresses globally and reset mod state if it's unexpectedly wrong. Best we can do. 1351 // Note that this thing _only_ adjusts the lock keys if necessary; the real SDL keypress handling happens elsewhere. 1352 document.sdlEventHandlerLockKeysCheck = function(event) { 1353 // don't try to adjust the state on the actual lock key presses; the normal key handler will catch that and adjust. 1354 if ((event.key != "CapsLock") && (event.key != "NumLock") && (event.key != "ScrollLock")) 1355 { 1356 #ifdef __wasm32__ 1357 _Emscripten_HandleLockKeysCheck(data, event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("ScrollLock")); 1358 #elif __wasm64__ 1359 _Emscripten_HandleLockKeysCheck(BigInt(data), event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("ScrollLock")); 1360 #endif 1361 } 1362 }; 1363 document.addEventListener("keydown", document.sdlEventHandlerLockKeysCheck); 1364 }, data); 1365 emscripten_set_keydown_callback(keyElement, data, 0, Emscripten_HandleKey); 1366 emscripten_set_keyup_callback(keyElement, data, 0, Emscripten_HandleKey); 1367 emscripten_set_keypress_callback(keyElement, data, 0, Emscripten_HandleKeyPress); 1368 } 1369 1370 emscripten_set_visibilitychange_callback(data, 0, Emscripten_HandleVisibilityChange); 1371 1372 emscripten_set_beforeunload_callback(data, Emscripten_HandleBeforeUnload); 1373 1374 // !!! FIXME: currently Emscripten doesn't have a Pointer Events functions like emscripten_set_*_callback, but we should use those when they do: 1375 // !!! FIXME: https://github.com/emscripten-core/emscripten/issues/7278#issuecomment-2280024621 1376 Emscripten_set_pointer_event_callbacks(data); 1377 1378 // !!! FIXME: currently Emscripten doesn't have a Drop Events functions like emscripten_set_*_callback, but we should use those when they do: 1379 Emscripten_set_drag_event_callbacks(data); 1380} 1381 1382void Emscripten_UnregisterEventHandlers(SDL_WindowData *data) 1383{ 1384 const char *keyElement; 1385 1386 // !!! FIXME: currently Emscripten doesn't have a Drop Events functions like emscripten_set_*_callback, but we should use those when they do: 1387 Emscripten_unset_drag_event_callbacks(data); 1388 1389 // !!! FIXME: currently Emscripten doesn't have a Pointer Events functions like emscripten_set_*_callback, but we should use those when they do: 1390 // !!! FIXME: https://github.com/emscripten-core/emscripten/issues/7278#issuecomment-2280024621 1391 Emscripten_unset_pointer_event_callbacks(data); 1392 1393 // only works due to having one window 1394 emscripten_set_wheel_callback(data->canvas_id, NULL, 0, NULL); 1395 1396 emscripten_set_orientationchange_callback(NULL, 0, NULL); 1397 1398 keyElement = Emscripten_GetKeyboardTargetElement(data->keyboard_element); 1399 if (keyElement) { 1400 emscripten_set_keydown_callback(keyElement, NULL, 0, NULL); 1401 emscripten_set_keyup_callback(keyElement, NULL, 0, NULL); 1402 emscripten_set_keypress_callback(keyElement, NULL, 0, NULL); 1403 MAIN_THREAD_EM_ASM_INT({ 1404 document.removeEventListener("keydown", document.sdlEventHandlerLockKeysCheck); 1405 }); 1406 } 1407 1408 emscripten_set_visibilitychange_callback(NULL, 0, NULL); 1409 1410 emscripten_set_beforeunload_callback(NULL, NULL); 1411} 1412 1413#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN 1414
[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.