SDL: emscripten: add window properties for canvas ID and keyboard element (#12509)

From 17b84dbcf3deb609b403cbaecf2dc7f7d36639dc Mon Sep 17 00:00:00 2001
From: Temdog007 <[EMAIL REDACTED]>
Date: Fri, 14 Mar 2025 09:39:46 -0700
Subject: [PATCH] emscripten: add window properties for canvas ID and keyboard
 element (#12509)

Move hints used by Emscripten to window properties. This change will be necessary if multiple windows for Emscripten will be supported in the future.

- Added Window Create Property SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID
- Added Window Create Property SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT
- Use hint SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR as override to SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID
- Use hint SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT as override to SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT
---
 include/SDL3/SDL_hints.h                    |  4 +---
 include/SDL3/SDL_video.h                    | 15 ++++++++++++++
 src/video/emscripten/SDL_emscriptenevents.c | 23 +++++++--------------
 src/video/emscripten/SDL_emscriptenvideo.c  | 13 +++++++++---
 src/video/emscripten/SDL_emscriptenvideo.h  |  1 +
 5 files changed, 35 insertions(+), 21 deletions(-)

diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 9c8ad3f828a5a..2793d09421cb5 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -711,8 +711,6 @@ extern "C" {
  *
  * This hint only applies to the emscripten platform.
  *
- * The default value is "#canvas"
- *
  * This hint should be set before creating a window.
  *
  * \since This hint is available since SDL 3.2.0.
@@ -726,7 +724,7 @@ extern "C" {
  *
  * The variable can be one of:
  *
- * - "#window": the javascript window object (default)
+ * - "#window": the javascript window object
  * - "#document": the javascript document object
  * - "#screen": the javascript window.screen object
  * - "#canvas": the WebGL canvas element
diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h
index a7afc3267b324..aa46d8aac65fe 100644
--- a/include/SDL3/SDL_video.h
+++ b/include/SDL3/SDL_video.h
@@ -1280,6 +1280,19 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren
  *
  * The window is implicitly shown if the "hidden" property is not set.
  *
+ * These are additional supported properties with Emscripten:
+ *
+ * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID`: the id given to the canvas element.
+ *    This should start with a '#' sign
+ * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT`: override the binding element
+ *   for keyboard inputs for this canvas. The variable can be one of:
+ * - "#window": the javascript window object (default)
+ * - "#document": the javascript document object
+ * - "#screen": the javascript window.screen object
+ * - "#canvas": the WebGL canvas element
+ * - "#none": Don't bind anything at all
+ * - any other string without a leading # sign applies to the element on the
+ *   page with that ID.
  * Windows with the "tooltip" and "menu" properties are popup windows and have
  * the behaviors and guidelines outlined in SDL_CreatePopupWindow().
  *
@@ -1341,6 +1354,8 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop
 #define SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER                  "SDL.window.create.win32.hwnd"
 #define SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER     "SDL.window.create.win32.pixel_format_hwnd"
 #define SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER                   "SDL.window.create.x11.window"
+#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID                "SDL.window.create.emscripten.canvas_id"
+#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT         "SDL.window.create.emscripten.keyboard_element"
 
 /**
  * Get the numeric ID of a window.
diff --git a/src/video/emscripten/SDL_emscriptenevents.c b/src/video/emscripten/SDL_emscriptenevents.c
index d9fa3c8fa3d67..fbea4416ec0a9 100644
--- a/src/video/emscripten/SDL_emscriptenevents.c
+++ b/src/video/emscripten/SDL_emscriptenevents.c
@@ -1005,14 +1005,8 @@ static void Emscripten_unset_drag_event_callbacks(SDL_WindowData *data)
     }, data->canvas_id);
 }
 
-static const char *Emscripten_GetKeyboardTargetElement()
+static const char *Emscripten_GetKeyboardTargetElement(const char *target)
 {
-    const char *target = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT);
-
-    if (!target || !*target) {
-        return EMSCRIPTEN_EVENT_TARGET_WINDOW;
-    }
-
     if (SDL_strcmp(target, "#none") == 0) {
         return NULL;
     } else if (SDL_strcmp(target, "#window") == 0) {
@@ -1053,8 +1047,7 @@ void Emscripten_RegisterEventHandlers(SDL_WindowData *data)
 
     emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, data, 0, Emscripten_HandlePointerLockChange);
 
-    // Keyboard events are awkward
-    keyElement = Emscripten_GetKeyboardTargetElement();
+    keyElement = Emscripten_GetKeyboardTargetElement(data->keyboard_element);
     if (keyElement) {
         emscripten_set_keydown_callback(keyElement, data, 0, Emscripten_HandleKey);
         emscripten_set_keyup_callback(keyElement, data, 0, Emscripten_HandleKey);
@@ -1079,7 +1072,7 @@ void Emscripten_RegisterEventHandlers(SDL_WindowData *data)
 
 void Emscripten_UnregisterEventHandlers(SDL_WindowData *data)
 {
-    const char *target;
+    const char *keyElement;
 
     // !!! FIXME: currently Emscripten doesn't have a Drop Events functions like emscripten_set_*_callback, but we should use those when they do:
     Emscripten_unset_drag_event_callbacks(data);
@@ -1111,11 +1104,11 @@ void Emscripten_UnregisterEventHandlers(SDL_WindowData *data)
 
     emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL);
 
-    target = Emscripten_GetKeyboardTargetElement();
-    if (target) {
-        emscripten_set_keydown_callback(target, NULL, 0, NULL);
-        emscripten_set_keyup_callback(target, NULL, 0, NULL);
-        emscripten_set_keypress_callback(target, NULL, 0, NULL);
+    keyElement = Emscripten_GetKeyboardTargetElement(data->keyboard_element);
+    if (keyElement) {
+        emscripten_set_keydown_callback(keyElement, NULL, 0, NULL);
+        emscripten_set_keyup_callback(keyElement, NULL, 0, NULL);
+        emscripten_set_keypress_callback(keyElement, NULL, 0, NULL);
     }
 
     emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, 0, NULL);
diff --git a/src/video/emscripten/SDL_emscriptenvideo.c b/src/video/emscripten/SDL_emscriptenvideo.c
index 14962686105d2..ad49e2fa1a137 100644
--- a/src/video/emscripten/SDL_emscriptenvideo.c
+++ b/src/video/emscripten/SDL_emscriptenvideo.c
@@ -293,12 +293,17 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window,
     }
 
     selector = SDL_GetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR);
-    if (!selector) {
-        selector = "#canvas";
+    if (!selector || !*selector) {
+        selector = SDL_GetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID, "#canvas");
     }
-
     wdata->canvas_id = SDL_strdup(selector);
 
+    selector = SDL_GetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT);
+    if (!selector || !*selector) {
+        selector = SDL_GetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT, "#window");
+    }
+    wdata->keyboard_element = SDL_strdup(selector);
+
     if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
         wdata->pixel_ratio = emscripten_get_device_pixel_ratio();
     } else {
@@ -397,6 +402,8 @@ static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
         emscripten_set_canvas_element_size(data->canvas_id, 0, 0);
         SDL_free(data->canvas_id);
 
+        SDL_free(data->keyboard_element);
+
         SDL_free(window->internal);
         window->internal = NULL;
     }
diff --git a/src/video/emscripten/SDL_emscriptenvideo.h b/src/video/emscripten/SDL_emscriptenvideo.h
index 892d074475eeb..db4751ce88531 100644
--- a/src/video/emscripten/SDL_emscriptenvideo.h
+++ b/src/video/emscripten/SDL_emscriptenvideo.h
@@ -36,6 +36,7 @@ struct SDL_WindowData
     SDL_GLContext gl_context;
 
     char *canvas_id;
+    char *keyboard_element;
 
     float pixel_ratio;