Atlas - SDL_emscriptenevents.c

Home / ext / SDL / src / video / emscripten Lines: 1 | Size: 57694 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 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 bool force = false; 523 524 // update pixel ratio 525 if (window_data->window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { 526 if (window_data->pixel_ratio != emscripten_get_device_pixel_ratio()) { 527 window_data->pixel_ratio = emscripten_get_device_pixel_ratio(); 528 force = true; 529 } 530 } 531 532 const bool fill_document = (Emscripten_fill_document_window == window_data->window); 533 const bool fullscreen = (window_data->window->flags & SDL_WINDOW_FULLSCREEN) != 0; // fullscreen windows can resize on Emscripten, and the canvas should fill it. 534 const bool resizable = (window_data->window->flags & SDL_WINDOW_RESIZABLE) != 0; 535 if (fill_document || fullscreen || resizable) { 536 double w, h; 537 if (fill_document || fullscreen) { 538 w = (double) uiEvent->windowInnerWidth; 539 h = (double) uiEvent->windowInnerHeight; 540 } else { 541 SDL_assert(window_data->window->flags & SDL_WINDOW_RESIZABLE); 542 w = window_data->window->w; 543 h = window_data->window->h; 544 // this will only work if the canvas size is set through css 545 if (window_data->external_size) { 546 emscripten_get_element_css_size(window_data->canvas_id, &w, &h); 547 } 548 } 549 550 emscripten_set_canvas_element_size(window_data->canvas_id, SDL_lroundf(w * window_data->pixel_ratio), SDL_lroundf(h * window_data->pixel_ratio)); 551 552 // set_canvas_size unsets this 553 if (!window_data->external_size && window_data->pixel_ratio != 1.0f) { 554 emscripten_set_element_css_size(window_data->canvas_id, w, h); 555 } 556 557 if (force) { 558 // force the event to trigger, so pixel ratio changes can be handled 559 window_data->window->w = 0; 560 window_data->window->h = 0; 561 } 562 563 SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(w), SDL_lroundf(h)); 564 } 565 566 return 0; 567} 568 569static EM_BOOL Emscripten_HandleResizeGlobal(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) 570{ 571 SDL_VideoDevice *device = userData; 572 bool prevent_default = false; 573 SDL_Window *window; 574 575 for (window = device->windows; window; window = window->next) { 576 prevent_default |= Emscripten_HandleResize(eventType, uiEvent, window->internal); 577 } 578 579 return prevent_default; 580} 581 582EM_BOOL 583Emscripten_HandleCanvasResize(int eventType, const void *reserved, void *userData) 584{ 585 // this is used during fullscreen changes 586 SDL_WindowData *window_data = userData; 587 588 if (window_data->fullscreen_resize) { 589 double css_w, css_h; 590 emscripten_get_element_css_size(window_data->canvas_id, &css_w, &css_h); 591 SDL_SendWindowEvent(window_data->window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(css_w), SDL_lroundf(css_h)); 592 } 593 594 return 0; 595} 596 597static EM_BOOL Emscripten_HandleVisibilityChange(int eventType, const EmscriptenVisibilityChangeEvent *visEvent, void *userData) 598{ 599 SDL_WindowData *window_data = userData; 600 SDL_SendWindowEvent(window_data->window, visEvent->hidden ? SDL_EVENT_WINDOW_HIDDEN : SDL_EVENT_WINDOW_SHOWN, 0, 0); 601 return 0; 602} 603 604static const char *Emscripten_HandleBeforeUnload(int eventType, const void *reserved, void *userData) 605{ 606 /* This event will need to be handled synchronously, e.g. using 607 SDL_AddEventWatch, as the page is being closed *now*. */ 608 // No need to send a SDL_EVENT_QUIT, the app won't get control again. 609 SDL_SendAppEvent(SDL_EVENT_TERMINATING); 610 return ""; // don't trigger confirmation dialog 611} 612 613static EM_BOOL Emscripten_HandleOrientationChange(int eventType, const EmscriptenOrientationChangeEvent *orientationChangeEvent, void *userData) 614{ 615 SDL_DisplayOrientation orientation; 616 switch (orientationChangeEvent->orientationIndex) { 617 #define CHECK_ORIENTATION(emsdk, sdl) case EMSCRIPTEN_ORIENTATION_##emsdk: orientation = SDL_ORIENTATION_##sdl; break 618 CHECK_ORIENTATION(LANDSCAPE_PRIMARY, LANDSCAPE); 619 CHECK_ORIENTATION(LANDSCAPE_SECONDARY, LANDSCAPE_FLIPPED); 620 CHECK_ORIENTATION(PORTRAIT_PRIMARY, PORTRAIT); 621 CHECK_ORIENTATION(PORTRAIT_SECONDARY, PORTRAIT_FLIPPED); 622 #undef CHECK_ORIENTATION 623 default: orientation = SDL_ORIENTATION_UNKNOWN; break; 624 } 625 626 SDL_WindowData *window_data = (SDL_WindowData *) userData; 627 SDL_SendDisplayEvent(SDL_GetVideoDisplayForWindow(window_data->window), SDL_EVENT_DISPLAY_ORIENTATION, orientation, 0); 628 629 // fake a UI event so we can tell the app the canvas might have resized. 630 EmscriptenUiEvent uiEvent; 631 SDL_zero(uiEvent); 632 uiEvent.documentBodyClientWidth = MAIN_THREAD_EM_ASM_INT( { return document.body.clientWidth; } ); 633 uiEvent.documentBodyClientHeight = MAIN_THREAD_EM_ASM_INT( { return document.body.clientHeight; } ); 634 uiEvent.windowInnerWidth = MAIN_THREAD_EM_ASM_INT( { return window.innerWidth; } ); 635 uiEvent.windowInnerHeight = MAIN_THREAD_EM_ASM_INT( { return window.innerHeight; } ); 636 uiEvent.windowOuterWidth = MAIN_THREAD_EM_ASM_INT( { return window.outerWidth; } ); 637 uiEvent.windowOuterHeight = MAIN_THREAD_EM_ASM_INT( { return window.outerHeight; } ); 638 uiEvent.scrollTop = MAIN_THREAD_EM_ASM_INT( { return window.pageXOffset; } ); 639 uiEvent.scrollLeft = MAIN_THREAD_EM_ASM_INT( { return window.pageYOffset; } ); 640 Emscripten_HandleResize(EMSCRIPTEN_EVENT_RESIZE, &uiEvent, userData); 641 642 return 0; 643} 644 645// IF YOU CHANGE THIS STRUCTURE, YOU NEED TO UPDATE THE JAVASCRIPT THAT FILLS IT IN: SDL3.makePointerEventCStruct, below. 646#define PTRTYPE_MOUSE 1 647#define PTRTYPE_TOUCH 2 648#define PTRTYPE_PEN 3 649typedef struct Emscripten_PointerEvent 650{ 651 int pointer_type; 652 int pointerid; 653 int button; 654 int buttons; 655 int down; 656 float movementX; 657 float movementY; 658 float targetX; 659 float targetY; 660 float pressure; 661 float tangential_pressure; 662 float tiltx; 663 float tilty; 664 float rotation; 665} Emscripten_PointerEvent; 666 667static void Emscripten_HandleMouseButton(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 668{ 669 Uint8 sdl_button; 670 bool down = false; 671 switch (event->button) { 672 #define CHECK_MOUSE_BUTTON(jsbutton, downflag, sdlbutton) case jsbutton: sdl_button = SDL_BUTTON_##sdlbutton; down = (event->down != 0) || ((event->buttons & downflag) != 0); break 673 CHECK_MOUSE_BUTTON(0, 1, LEFT); 674 CHECK_MOUSE_BUTTON(1, 4, MIDDLE); 675 CHECK_MOUSE_BUTTON(2, 2, RIGHT); 676 CHECK_MOUSE_BUTTON(3, 8, X1); 677 CHECK_MOUSE_BUTTON(4, 16, X2); 678 #undef CHECK_MOUSE_BUTTON 679 default: sdl_button = 0; break; 680 } 681 682 if (sdl_button) { 683 const SDL_Mouse *mouse = SDL_GetMouse(); 684 SDL_assert(mouse != NULL); 685 686 if (down) { 687 if (mouse->relative_mode && !window_data->has_pointer_lock) { 688 emscripten_request_pointerlock(window_data->canvas_id, 0); // try to regrab lost pointer lock. 689 } 690 } 691 692 SDL_SendMouseButton(0, window_data->window, SDL_DEFAULT_MOUSE_ID, sdl_button, down); 693 694 // We have an imaginary mouse capture, because we need SDL to not drop our imaginary mouse focus when we leave the canvas. 695 if (mouse->auto_capture) { 696 if (SDL_GetMouseState(NULL, NULL) != 0) { 697 window_data->window->flags |= SDL_WINDOW_MOUSE_CAPTURE; 698 } else { 699 window_data->window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE; 700 } 701 } 702 703 if (!down && window_data->mouse_focus_loss_pending) { 704 window_data->mouse_focus_loss_pending = (window_data->window->flags & SDL_WINDOW_MOUSE_CAPTURE) != 0; 705 if (!window_data->mouse_focus_loss_pending) { 706 SDL_SetMouseFocus(NULL); 707 } 708 } 709 } 710} 711 712static void Emscripten_UpdateMouseFromEvent(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 713{ 714 SDL_assert(event->pointer_type == PTRTYPE_MOUSE); 715 716 // rescale (in case canvas is being scaled) 717 double client_w, client_h; 718 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h); 719 const double xscale = window_data->window->w / client_w; 720 const double yscale = window_data->window->h / client_h; 721 722 const bool isPointerLocked = window_data->has_pointer_lock; 723 float mx, my; 724 if (isPointerLocked) { 725 mx = (float)(event->movementX * xscale); 726 my = (float)(event->movementY * yscale); 727 } else { 728 mx = (float)(event->targetX * xscale); 729 my = (float)(event->targetY * yscale); 730 } 731 732 SDL_SendMouseMotion(0, window_data->window, SDL_DEFAULT_MOUSE_ID, isPointerLocked, mx, my); 733 734 Emscripten_HandleMouseButton(window_data, event); 735} 736 737static void Emscripten_UpdateTouchFromEvent(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 738{ 739 SDL_assert(event->pointer_type == PTRTYPE_TOUCH); 740 741 const SDL_TouchID deviceId = 1; 742 if (SDL_AddTouch(deviceId, SDL_TOUCH_DEVICE_DIRECT, "") < 0) { 743 return; 744 } 745 746 double client_w, client_h; 747 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h); 748 749 const SDL_FingerID id = event->pointerid + 1; 750 float x, y; 751 if (client_w <= 1) { 752 x = 0.5f; 753 } else { 754 x = event->targetX / (client_w - 1); 755 } 756 if (client_h <= 1) { 757 y = 0.5f; 758 } else { 759 y = event->targetY / (client_h - 1); 760 } 761 762 const bool down = (event->buttons & 1) != 0; 763 if (event->button == 0) { // touch is starting or ending if this is zero (-1 means "no change"). 764 if (down) { 765 SDL_SendTouch(0, deviceId, id, window_data->window, SDL_EVENT_FINGER_DOWN, x, y, 1.0f); 766 } 767 } 768 769 SDL_SendTouchMotion(0, deviceId, id, window_data->window, x, y, 1.0f); 770 771 if (event->button == 0) { // touch is starting or ending if this is zero (-1 means "no change"). 772 if (!down) { 773 SDL_SendTouch(0, deviceId, id, window_data->window, SDL_EVENT_FINGER_UP, x, y, 1.0f); 774 } 775 } 776} 777 778static void Emscripten_UpdatePenFromEvent(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 779{ 780 SDL_assert(event->pointer_type == PTRTYPE_PEN); 781 const SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) 1); // something > 0 for the single pen handle. 782 if (pen) { 783 // rescale (in case canvas is being scaled) 784 double client_w, client_h; 785 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h); 786 const double xscale = window_data->window->w / client_w; 787 const double yscale = window_data->window->h / client_h; 788 789 const bool isPointerLocked = window_data->has_pointer_lock; 790 float mx, my; 791 if (isPointerLocked) { 792 mx = (float)(event->movementX * xscale); 793 my = (float)(event->movementY * yscale); 794 } else { 795 mx = (float)(event->targetX * xscale); 796 my = (float)(event->targetY * yscale); 797 } 798 799 SDL_SendPenMotion(0, pen, window_data->window, mx, my); 800 801 if (event->button == 0) { // pen touch 802 bool down = ((event->buttons & 1) != 0); 803 SDL_SendPenTouch(0, pen, window_data->window, false, down); 804 } else if (event->button == 5) { // eraser touch...? Not sure if this is right... 805 bool down = ((event->buttons & 32) != 0); 806 SDL_SendPenTouch(0, pen, window_data->window, true, down); 807 } else if (event->button == 1) { 808 bool down = ((event->buttons & 4) != 0); 809 SDL_SendPenButton(0, pen, window_data->window, 2, down); 810 } else if (event->button == 2) { 811 bool down = ((event->buttons & 2) != 0); 812 SDL_SendPenButton(0, pen, window_data->window, 1, down); 813 } 814 815 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_PRESSURE, event->pressure); 816 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_TANGENTIAL_PRESSURE, event->tangential_pressure); 817 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_XTILT, event->tiltx); 818 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_YTILT, event->tilty); 819 SDL_SendPenAxis(0, pen, window_data->window, SDL_PEN_AXIS_ROTATION, event->rotation); 820 } 821} 822 823static void Emscripten_UpdatePointerFromEvent(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 824{ 825 SDL_assert(event != NULL); 826 if (event->pointer_type == PTRTYPE_MOUSE) { 827 Emscripten_UpdateMouseFromEvent(window_data, event); 828 } else if (event->pointer_type == PTRTYPE_TOUCH) { 829 Emscripten_UpdateTouchFromEvent(window_data, event); 830 } else if (event->pointer_type == PTRTYPE_PEN) { 831 Emscripten_UpdatePenFromEvent(window_data, event); 832 } else { 833 SDL_assert(!"Unexpected pointer event type"); 834 } 835} 836 837static void Emscripten_HandleMouseFocus(SDL_WindowData *window_data, const Emscripten_PointerEvent *event, bool isenter) 838{ 839 SDL_assert(event->pointer_type == PTRTYPE_MOUSE); 840 841 const bool isPointerLocked = window_data->has_pointer_lock; 842 843 if (!isPointerLocked) { 844 // rescale (in case canvas is being scaled) 845 float mx, my; 846 double client_w, client_h; 847 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h); 848 849 mx = (float)(event->targetX * (window_data->window->w / client_w)); 850 my = (float)(event->targetY * (window_data->window->h / client_h)); 851 SDL_SendMouseMotion(0, window_data->window, SDL_GLOBAL_MOUSE_ID, isPointerLocked, mx, my); 852 } 853 854 if (isenter && window_data->mouse_focus_loss_pending) { 855 window_data->mouse_focus_loss_pending = false; // just drop the state, but don't send the enter event. 856 } else if (!isenter && (window_data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { 857 window_data->mouse_focus_loss_pending = true; // waiting on a mouse button to let go before we send the mouse focus update. 858 } else { 859 SDL_SetMouseFocus(isenter ? window_data->window : NULL); 860 } 861} 862 863static void Emscripten_HandlePenEnter(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 864{ 865 SDL_assert(event->pointer_type == PTRTYPE_PEN); 866 867 // 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. 868 // as such, we only expose a single pen, and when the touch ends, we say it lost proximity instead of the calling SDL_RemovePenDevice(). 869 870 SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) 1); // something > 0 for the single pen handle. 871 if (pen) { 872 SDL_SendPenProximity(0, pen, window_data->window, true, true); 873 } else { 874 // Web browsers offer almost none of this information as specifics, but can without warning offer any of these specific things. 875 SDL_PenInfo peninfo; 876 SDL_zero(peninfo); 877 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; 878 peninfo.max_tilt = 90.0f; 879 peninfo.num_buttons = 2; 880 peninfo.subtype = SDL_PEN_TYPE_PEN; 881 SDL_AddPenDevice(0, NULL, window_data->window, &peninfo, (void *) (size_t) 1, true); 882 } 883 884 Emscripten_UpdatePenFromEvent(window_data, event); 885} 886 887EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerEnter(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 888{ 889 SDL_assert(event != NULL); 890 if (event->pointer_type == PTRTYPE_MOUSE) { 891 Emscripten_HandleMouseFocus(window_data, event, true); 892 } else if (event->pointer_type == PTRTYPE_PEN) { 893 Emscripten_HandlePenEnter(window_data, event); 894 } else if (event->pointer_type == PTRTYPE_TOUCH) { 895 // do nothing. 896 } else { 897 SDL_assert(!"Unexpected pointer event type"); 898 } 899} 900 901static void Emscripten_HandlePenLeave(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 902{ 903 const SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) 1); // something > 0 for the single pen handle. 904 if (pen) { 905 Emscripten_UpdatePointerFromEvent(window_data, event); // last data updates? 906 SDL_SendPenProximity(0, pen, window_data->window, false, false); 907 } 908} 909 910static void Emscripten_HandleTouchCancel(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 911{ 912 SDL_assert(event->pointer_type == PTRTYPE_TOUCH); 913 914 const SDL_TouchID deviceId = 1; 915 if (SDL_AddTouch(deviceId, SDL_TOUCH_DEVICE_DIRECT, "") < 0) { 916 return; 917 } 918 919 double client_w, client_h; 920 emscripten_get_element_css_size(window_data->canvas_id, &client_w, &client_h); 921 922 const SDL_FingerID id = event->pointerid + 1; 923 float x, y; 924 if (client_w <= 1) { 925 x = 0.5f; 926 } else { 927 x = event->targetX / (client_w - 1); 928 } 929 if (client_h <= 1) { 930 y = 0.5f; 931 } else { 932 y = event->targetY / (client_h - 1); 933 } 934 935 SDL_SendTouch(0, deviceId, id, window_data->window, SDL_EVENT_FINGER_CANCELED, x, y, 1.0f); 936} 937 938EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerLeave(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 939{ 940 SDL_assert(event != NULL); 941 if (event->pointer_type == PTRTYPE_MOUSE) { 942 Emscripten_HandleMouseFocus(window_data, event, false); 943 } else if (event->pointer_type == PTRTYPE_PEN) { 944 Emscripten_HandlePenLeave(window_data, event); 945 } else if (event->pointer_type == PTRTYPE_TOUCH) { 946 Emscripten_HandleTouchCancel(window_data, event); 947 } else { 948 SDL_assert(!"Unexpected pointer event type"); 949 } 950} 951 952EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerGeneric(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) 953{ 954 SDL_assert(event != NULL); 955 Emscripten_UpdatePointerFromEvent(window_data, event); 956} 957 958static void Emscripten_prep_pointer_event_callbacks(void) 959{ 960 MAIN_THREAD_EM_ASM({ 961 var SDL3 = Module['SDL3']; 962 963 if (SDL3.makePointerEventCStruct === undefined) { 964 SDL3.makePointerEventCStruct = function(left, top, event) { 965 var ptrtype = 0; 966 if (event.pointerType == "mouse") { 967 ptrtype = 1; 968 } else if (event.pointerType == "touch") { 969 ptrtype = 2; 970 } else if (event.pointerType == "pen") { 971 ptrtype = 3; 972 } else { 973 return 0; 974 } 975 976 var ptr = _SDL_malloc($0); 977 if (ptr != 0) { 978 var idx = SDL3.CPtrToHeap32Index(ptr); 979 HEAP32[idx++] = ptrtype; 980 HEAP32[idx++] = event.pointerId; 981 HEAP32[idx++] = (typeof(event.button) !== "undefined") ? event.button : -1; 982 HEAP32[idx++] = event.buttons; 983 HEAP32[idx++] = (event.type == "pointerdown") ? 1 : 0; 984 HEAPF32[idx++] = event.movementX; 985 HEAPF32[idx++] = event.movementY; 986 HEAPF32[idx++] = event.clientX - left; 987 HEAPF32[idx++] = event.clientY - top; 988 if (ptrtype == 3) { 989 HEAPF32[idx++] = event.pressure; 990 HEAPF32[idx++] = event.tangentialPressure; 991 HEAPF32[idx++] = event.tiltX; 992 HEAPF32[idx++] = event.tiltY; 993 HEAPF32[idx++] = event.twist; 994 } 995 } 996 return ptr; 997 }; 998 } 999 }, sizeof (Emscripten_PointerEvent)); 1000} 1001 1002static void Emscripten_set_pointer_event_callbacks(SDL_WindowData *data) 1003{ 1004 Emscripten_prep_pointer_event_callbacks(); 1005 1006 MAIN_THREAD_EM_ASM({ 1007 var target = document.querySelector(UTF8ToString($1)); 1008 if (target) { 1009 var SDL3 = Module['SDL3']; 1010 var data = $0; 1011 target.sdlEventHandlerPointerEnter = function(event) { 1012 var rect = target.getBoundingClientRect(); 1013 var d = SDL3.makePointerEventCStruct(rect.left, rect.top, event); 1014 if (d != 0) 1015 { 1016 _Emscripten_HandlePointerEnter(SDL3.JSVarToCPtr(data), d); 1017 _SDL_free(d); 1018 } 1019 }; 1020 target.sdlEventHandlerPointerLeave = function(event) { 1021 var rect = target.getBoundingClientRect(); 1022 var d = SDL3.makePointerEventCStruct(rect.left, rect.top, event); 1023 if (d != 0) 1024 { 1025 _Emscripten_HandlePointerLeave(SDL3.JSVarToCPtr(data), d); 1026 _SDL_free(d); 1027 } 1028 }; 1029 target.sdlEventHandlerPointerGeneric = function(event) { 1030 var rect = target.getBoundingClientRect(); 1031 var d = SDL3.makePointerEventCStruct(rect.left, rect.top, event); 1032 if (d != 0) 1033 { 1034 _Emscripten_HandlePointerGeneric(SDL3.JSVarToCPtr(data), d); 1035 _SDL_free(d); 1036 } 1037 }; 1038 1039 target.style.touchAction = "none"; // or mobile devices will scroll as your touch moves across the element. 1040 target.addEventListener("pointerenter", target.sdlEventHandlerPointerEnter); 1041 target.addEventListener("pointerleave", target.sdlEventHandlerPointerLeave); 1042 target.addEventListener("pointercancel", target.sdlEventHandlerPointerLeave); 1043 target.addEventListener("pointerdown", target.sdlEventHandlerPointerGeneric); 1044 target.addEventListener("pointermove", target.sdlEventHandlerPointerGeneric); 1045 target.addEventListener("pointerup", target.sdlEventHandlerPointerGeneric); 1046 } 1047 }, data, data->canvas_id); 1048} 1049 1050static void Emscripten_unset_pointer_event_callbacks(SDL_WindowData *data) 1051{ 1052 MAIN_THREAD_EM_ASM({ 1053 var target = document.querySelector(UTF8ToString($0)); 1054 if (target) { 1055 target.removeEventListener("pointerenter", target.sdlEventHandlerPointerEnter); 1056 target.removeEventListener("pointerleave", target.sdlEventHandlerPointerLeave); 1057 target.removeEventListener("pointercancel", target.sdlEventHandlerPointerLeave); 1058 target.removeEventListener("pointerdown", target.sdlEventHandlerPointerGeneric); 1059 target.removeEventListener("pointermove", target.sdlEventHandlerPointerGeneric); 1060 target.removeEventListener("pointerup", target.sdlEventHandlerPointerGeneric); 1061 target.style.touchAction = ""; // let mobile devices scroll again as your touch moves across the element. 1062 target.sdlEventHandlerPointerEnter = undefined; 1063 target.sdlEventHandlerPointerLeave = undefined; 1064 target.sdlEventHandlerPointerGeneric = undefined; 1065 } 1066 }, data->canvas_id); 1067} 1068 1069EMSCRIPTEN_KEEPALIVE void Emscripten_HandleMouseButtonUpGlobal(SDL_VideoDevice *device, const Emscripten_PointerEvent *event) 1070{ 1071 SDL_assert(device != NULL); 1072 SDL_assert(event != NULL); 1073 if (event->pointer_type == PTRTYPE_MOUSE) { 1074 for (SDL_Window *window = device->windows; window; window = window->next) { 1075 Emscripten_HandleMouseButton(window->internal, event); 1076 } 1077 } 1078} 1079 1080static void Emscripten_set_global_mouseup_callback(SDL_VideoDevice *device) 1081{ 1082 Emscripten_prep_pointer_event_callbacks(); 1083 1084 MAIN_THREAD_EM_ASM({ 1085 var target = document; 1086 if (target) { 1087 target.sdlEventHandlerMouseButtonUpGlobal = function(event) { 1088 var SDL3 = Module['SDL3']; 1089 var d = SDL3.makePointerEventCStruct(0, 0, event); 1090 if (d != 0) 1091 { 1092 _Emscripten_HandleMouseButtonUpGlobal(SDL3.JSVarToCPtr($0), d); 1093 _SDL_free(d); 1094 } 1095 }; 1096 target.addEventListener("pointerup", target.sdlEventHandlerMouseButtonUpGlobal); 1097 } 1098 }, device); 1099} 1100 1101static void Emscripten_unset_global_mouseup_callback(SDL_VideoDevice *device) 1102{ 1103 MAIN_THREAD_EM_ASM({ 1104 var target = document; 1105 if (target) { 1106 target.removeEventListener("pointerup", target.sdlEventHandlerMouseButtonUpGlobal); 1107 target.sdlEventHandlerMouseButtonUpGlobal = undefined; 1108 } 1109 }); 1110} 1111 1112// IF YOU CHANGE THIS STRUCTURE, YOU NEED TO UPDATE THE JAVASCRIPT THAT FILLS IT IN: makeDropEventCStruct, below. 1113typedef struct Emscripten_DropEvent 1114{ 1115 int x; 1116 int y; 1117} Emscripten_DropEvent; 1118 1119EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragEvent(SDL_WindowData *window_data, const Emscripten_DropEvent *event) 1120{ 1121 SDL_SendDropPosition(window_data->window, event->x, event->y); 1122} 1123 1124EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragCompleteEvent(SDL_WindowData *window_data) 1125{ 1126 SDL_SendDropComplete(window_data->window); 1127} 1128 1129EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragTextEvent(SDL_WindowData *window_data, char *text) 1130{ 1131 SDL_SendDropText(window_data->window, text); 1132} 1133 1134EMSCRIPTEN_KEEPALIVE void Emscripten_SendDragFileEvent(SDL_WindowData *window_data, char *filename) 1135{ 1136 SDL_SendDropFile(window_data->window, NULL, filename); 1137} 1138 1139EM_JS_DEPS(dragndrop, "$writeArrayToMemory"); 1140 1141static void Emscripten_set_drag_event_callbacks(SDL_WindowData *data) 1142{ 1143 MAIN_THREAD_EM_ASM({ 1144 var target = document.querySelector(UTF8ToString($1)); 1145 if (target) { 1146 var data = $0; 1147 var SDL3 = Module['SDL3']; 1148 1149 var makeDropEventCStruct = function(event) { 1150 var ptr = 0; 1151 ptr = _SDL_malloc($2); 1152 if (ptr != 0) { 1153 var idx = ptr >> 2; 1154 var rect = target.getBoundingClientRect(); 1155 HEAP32[idx++] = event.clientX - rect.left; 1156 HEAP32[idx++] = event.clientY - rect.top; 1157 } 1158 return ptr; 1159 }; 1160 1161 SDL3.eventHandlerDropDragover = function(event) { 1162 event.preventDefault(); 1163 var d = makeDropEventCStruct(event); if (d != 0) { _Emscripten_SendDragEvent(data, d); _SDL_free(d); } 1164 }; 1165 target.addEventListener("dragover", SDL3.eventHandlerDropDragover); 1166 1167 SDL3.drop_count = 0; 1168 1169 // FS.* functions throw exceptions when there are errors (such as the temp dir already existing), 1170 // but we ignore all of these in a catch handler; you just won't get the drop event if there's a problem. 1171 try { FS.mkdir("/tmp/filedrop"); } catch (e) {} 1172 1173 SDL3.eventHandlerDropDrop = function(event) { 1174 event.preventDefault(); 1175 if (event.dataTransfer.types.includes("text/plain")) { 1176 let plain_text = stringToNewUTF8(event.dataTransfer.getData("text/plain")); 1177 _Emscripten_SendDragTextEvent(data, plain_text); 1178 _Emscripten_force_free(plain_text); 1179 } else if (event.dataTransfer.types.includes("Files")) { 1180 let files_read = 0; 1181 const files_to_read = event.dataTransfer.files.length; 1182 for (let i = 0; i < files_to_read; i++) { 1183 const file = event.dataTransfer.files.item(i); 1184 const file_reader = new FileReader(); 1185 file_reader.readAsArrayBuffer(file); 1186 file_reader.onload = function(event) { 1187 const fs_dropdir = `/tmp/filedrop/${SDL3.drop_count}`; 1188 SDL3.drop_count += 1; 1189 1190 const fs_filepath = `${fs_dropdir}/${file.name}`; 1191 const c_fs_filepath = stringToNewUTF8(fs_filepath); 1192 const contents_array8 = new Uint8Array(event.target.result); 1193 1194 try { 1195 FS.mkdir(fs_dropdir); 1196 var stream = FS.open(fs_filepath, "w"); 1197 FS.write(stream, contents_array8, 0, contents_array8.length, 0); 1198 FS.close(stream); 1199 _Emscripten_SendDragFileEvent(data, c_fs_filepath); 1200 } catch (e) { 1201 // if this threw an exception at any point, we skip this drop event. Sorry! 1202 } 1203 _Emscripten_force_free(c_fs_filepath); 1204 onFileRead(); 1205 }; 1206 file_reader.onerror = function(event) { 1207 // Handle when error occurs to ensure that the drag event can still complete 1208 onFileRead(); 1209 }; 1210 } 1211 function onFileRead() { 1212 ++files_read; 1213 if (files_read === files_to_read) { 1214 _Emscripten_SendDragCompleteEvent(data); 1215 } 1216 } 1217 } 1218 _Emscripten_SendDragCompleteEvent(data); 1219 }; 1220 target.addEventListener("drop", SDL3.eventHandlerDropDrop); 1221 1222 SDL3.eventHandlerDropDragend = function(event) { 1223 event.preventDefault(); 1224 _Emscripten_SendDragCompleteEvent(data); 1225 }; 1226 target.addEventListener("dragend", SDL3.eventHandlerDropDragend); 1227 target.addEventListener("dragleave", SDL3.eventHandlerDropDragend); 1228 } 1229 }, data, data->canvas_id, sizeof (Emscripten_DropEvent)); 1230} 1231 1232static void Emscripten_unset_drag_event_callbacks(SDL_WindowData *data) 1233{ 1234 MAIN_THREAD_EM_ASM({ 1235 var target = document.querySelector(UTF8ToString($0)); 1236 if (target) { 1237 var SDL3 = Module['SDL3']; 1238 target.removeEventListener("dragleave", SDL3.eventHandlerDropDragend); 1239 target.removeEventListener("dragend", SDL3.eventHandlerDropDragend); 1240 target.removeEventListener("drop", SDL3.eventHandlerDropDrop); 1241 SDL3.drop_count = undefined; 1242 1243 function recursive_remove(dirpath) { 1244 FS.readdir(dirpath).forEach((filename) => { 1245 const p = `${dirpath}/${filename}`; 1246 const p_s = FS.stat(p); 1247 if (FS.isFile(p_s.mode)) { 1248 FS.unlink(p); 1249 } else if (FS.isDir(p)) { 1250 recursive_remove(p); 1251 } 1252 }); 1253 FS.rmdir(dirpath); 1254 }("/tmp/filedrop"); 1255 1256 FS.rmdir("/tmp/filedrop"); 1257 target.removeEventListener("dragover", SDL3.eventHandlerDropDragover); 1258 SDL3.eventHandlerDropDragover = undefined; 1259 SDL3.eventHandlerDropDrop = undefined; 1260 SDL3.eventHandlerDropDragend = undefined; 1261 } 1262 }, data->canvas_id); 1263} 1264 1265static const char *Emscripten_GetKeyboardTargetElement(const char *target) 1266{ 1267 if (SDL_strcmp(target, "#none") == 0) { 1268 return NULL; 1269 } else if (SDL_strcmp(target, "#window") == 0) { 1270 return EMSCRIPTEN_EVENT_TARGET_WINDOW; 1271 } else if (SDL_strcmp(target, "#document") == 0) { 1272 return EMSCRIPTEN_EVENT_TARGET_DOCUMENT; 1273 } else if (SDL_strcmp(target, "#screen") == 0) { 1274 return EMSCRIPTEN_EVENT_TARGET_SCREEN; 1275 } 1276 1277 return target; 1278} 1279 1280void Emscripten_RegisterGlobalEventHandlers(SDL_VideoDevice *device) 1281{ 1282 Emscripten_set_global_mouseup_callback(device); 1283 1284 emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, device, 0, Emscripten_HandleFocus); 1285 emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, device, 0, Emscripten_HandleFocus); 1286 1287 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, device, 0, Emscripten_HandlePointerLockChangeGlobal); 1288 1289 emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, device, 0, Emscripten_HandleFullscreenChangeGlobal); 1290 1291 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, device, 0, Emscripten_HandleResizeGlobal); 1292} 1293 1294void Emscripten_UnregisterGlobalEventHandlers(SDL_VideoDevice *device) 1295{ 1296 Emscripten_unset_global_mouseup_callback(device); 1297 1298 emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL); 1299 emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL); 1300 1301 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL); 1302 1303 emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL); 1304 1305 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL); 1306} 1307 1308EMSCRIPTEN_KEEPALIVE void Emscripten_HandleLockKeysCheck(SDL_WindowData *window_data, EM_BOOL capslock, EM_BOOL numlock, EM_BOOL scrolllock) 1309{ 1310 const SDL_Keymod new_mods = (capslock ? SDL_KMOD_CAPS : 0) | (numlock ? SDL_KMOD_NUM : 0) | (scrolllock ? SDL_KMOD_SCROLL : 0); 1311 SDL_Keymod modstate = SDL_GetModState(); 1312 if ((modstate & (SDL_KMOD_CAPS|SDL_KMOD_NUM|SDL_KMOD_SCROLL)) != new_mods) { 1313 modstate &= ~(SDL_KMOD_CAPS|SDL_KMOD_NUM|SDL_KMOD_SCROLL); 1314 modstate |= new_mods; 1315 SDL_SetModState(modstate); 1316 } 1317} 1318 1319void Emscripten_RegisterEventHandlers(SDL_WindowData *data) 1320{ 1321 const char *keyElement; 1322 1323 // There is only one window and that window is the canvas 1324 emscripten_set_wheel_callback(data->canvas_id, data, 0, Emscripten_HandleWheel); 1325 1326 emscripten_set_orientationchange_callback(data, 0, Emscripten_HandleOrientationChange); 1327 1328 keyElement = Emscripten_GetKeyboardTargetElement(data->keyboard_element); 1329 if (keyElement) { 1330 MAIN_THREAD_EM_ASM_INT({ 1331 var data = $0; 1332 // 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 1333 // 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. 1334 // Note that this thing _only_ adjusts the lock keys if necessary; the real SDL keypress handling happens elsewhere. 1335 document.sdlEventHandlerLockKeysCheck = function(event) { 1336 // don't try to adjust the state on the actual lock key presses; the normal key handler will catch that and adjust. 1337 if ((event.key != "CapsLock") && (event.key != "NumLock") && (event.key != "ScrollLock")) 1338 { 1339 _Emscripten_HandleLockKeysCheck(Module['SDL3'].JSVarToCPtr(data), event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("ScrollLock")); 1340 } 1341 }; 1342 document.addEventListener("keydown", document.sdlEventHandlerLockKeysCheck); 1343 }, data); 1344 emscripten_set_keydown_callback(keyElement, data, 0, Emscripten_HandleKey); 1345 emscripten_set_keyup_callback(keyElement, data, 0, Emscripten_HandleKey); 1346 emscripten_set_keypress_callback(keyElement, data, 0, Emscripten_HandleKeyPress); 1347 } 1348 1349 emscripten_set_visibilitychange_callback(data, 0, Emscripten_HandleVisibilityChange); 1350 1351 emscripten_set_beforeunload_callback(data, Emscripten_HandleBeforeUnload); 1352 1353 // !!! FIXME: currently Emscripten doesn't have a Pointer Events functions like emscripten_set_*_callback, but we should use those when they do: 1354 // !!! FIXME: https://github.com/emscripten-core/emscripten/issues/7278#issuecomment-2280024621 1355 Emscripten_set_pointer_event_callbacks(data); 1356 1357 // !!! FIXME: currently Emscripten doesn't have a Drop Events functions like emscripten_set_*_callback, but we should use those when they do: 1358 Emscripten_set_drag_event_callbacks(data); 1359} 1360 1361void Emscripten_UnregisterEventHandlers(SDL_WindowData *data) 1362{ 1363 const char *keyElement; 1364 1365 // !!! FIXME: currently Emscripten doesn't have a Drop Events functions like emscripten_set_*_callback, but we should use those when they do: 1366 Emscripten_unset_drag_event_callbacks(data); 1367 1368 // !!! FIXME: currently Emscripten doesn't have a Pointer Events functions like emscripten_set_*_callback, but we should use those when they do: 1369 // !!! FIXME: https://github.com/emscripten-core/emscripten/issues/7278#issuecomment-2280024621 1370 Emscripten_unset_pointer_event_callbacks(data); 1371 1372 // only works due to having one window 1373 emscripten_set_wheel_callback(data->canvas_id, NULL, 0, NULL); 1374 1375 emscripten_set_orientationchange_callback(NULL, 0, NULL); 1376 1377 keyElement = Emscripten_GetKeyboardTargetElement(data->keyboard_element); 1378 if (keyElement) { 1379 emscripten_set_keydown_callback(keyElement, NULL, 0, NULL); 1380 emscripten_set_keyup_callback(keyElement, NULL, 0, NULL); 1381 emscripten_set_keypress_callback(keyElement, NULL, 0, NULL); 1382 MAIN_THREAD_EM_ASM_INT({ 1383 document.removeEventListener("keydown", document.sdlEventHandlerLockKeysCheck); 1384 }); 1385 } 1386 1387 emscripten_set_visibilitychange_callback(NULL, 0, NULL); 1388 1389 emscripten_set_beforeunload_callback(NULL, NULL); 1390} 1391 1392#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN 1393
[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.