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