SDL: Fixed touch normalized coordinates

From 9302d7732dfbb3c6364da142efd06ea9846206c1 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 5 Nov 2023 00:03:23 -0700
Subject: [PATCH] Fixed touch normalized coordinates

When converting normalized coordinates to pixel coordinates, the valid range is 0 to (width or height) - 1, and the pixel coordinate of width or height is past the edge of the window.

Fixes https://github.com/libsdl-org/SDL/issues/2913
---
 .../main/java/org/libsdl/app/SDLSurface.java  | 30 +++++++++++++++----
 src/video/emscripten/SDL_emscriptenevents.c   | 12 ++++++--
 src/video/n3ds/SDL_n3dstouch.c                |  4 +--
 src/video/vita/SDL_vitatouch.c                | 14 +++++++--
 src/video/wayland/SDL_waylandevents.c         | 14 +++++++--
 src/video/windows/SDL_windowsevents.c         | 14 +++++++--
 6 files changed, 72 insertions(+), 16 deletions(-)

diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java b/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java
index c4c14491455c..fb98f1b624fa 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java
@@ -196,6 +196,24 @@ public boolean onKey(View v, int keyCode, KeyEvent event) {
         return SDLActivity.handleKeyEvent(v, keyCode, event, null);
     }
 
+    private float getNormalizedX(float x)
+    {
+        if (mWidth <= 1) {
+            return 0.5f;
+        } else {
+            return (x / (mWidth - 1));
+        }
+    }
+
+    private float getNormalizedY(float y)
+    {
+        if (mHeight <= 1) {
+            return 0.5f;
+        } else {
+            return (y / (mHeight - 1));
+        }
+    }
+
     // Touch events
     @Override
     public boolean onTouch(View v, MotionEvent event) {
@@ -242,8 +260,8 @@ public boolean onTouch(View v, MotionEvent event) {
                 case MotionEvent.ACTION_MOVE:
                     for (i = 0; i < pointerCount; i++) {
                         pointerFingerId = event.getPointerId(i);
-                        x = event.getX(i) / mWidth;
-                        y = event.getY(i) / mHeight;
+                        x = getNormalizedX(event.getX(i));
+                        y = getNormalizedY(event.getY(i));
                         p = event.getPressure(i);
                         if (p > 1.0f) {
                             // may be larger than 1.0f on some devices
@@ -267,8 +285,8 @@ public boolean onTouch(View v, MotionEvent event) {
                     }
 
                     pointerFingerId = event.getPointerId(i);
-                    x = event.getX(i) / mWidth;
-                    y = event.getY(i) / mHeight;
+                    x = getNormalizedX(event.getX(i));
+                    y = getNormalizedY(event.getY(i));
                     p = event.getPressure(i);
                     if (p > 1.0f) {
                         // may be larger than 1.0f on some devices
@@ -281,8 +299,8 @@ public boolean onTouch(View v, MotionEvent event) {
                 case MotionEvent.ACTION_CANCEL:
                     for (i = 0; i < pointerCount; i++) {
                         pointerFingerId = event.getPointerId(i);
-                        x = event.getX(i) / mWidth;
-                        y = event.getY(i) / mHeight;
+                        x = getNormalizedX(event.getX(i));
+                        y = getNormalizedY(event.getY(i));
                         p = event.getPressure(i);
                         if (p > 1.0f) {
                             // may be larger than 1.0f on some devices
diff --git a/src/video/emscripten/SDL_emscriptenevents.c b/src/video/emscripten/SDL_emscriptenevents.c
index e862bbc89778..e25dff184d51 100644
--- a/src/video/emscripten/SDL_emscriptenevents.c
+++ b/src/video/emscripten/SDL_emscriptenevents.c
@@ -759,8 +759,16 @@ static EM_BOOL Emscripten_HandleTouch(int eventType, const EmscriptenTouchEvent
         }
 
         id = touchEvent->touches[i].identifier;
-        x = touchEvent->touches[i].targetX / client_w;
-        y = touchEvent->touches[i].targetY / client_h;
+        if (client_w <= 1) {
+            x = 0.5f;
+        } else {
+            x = touchEvent->touches[i].targetX / (client_w - 1);
+        }
+        if (client_h <= 1) {
+            y = 0.5f;
+        } else {
+            y = touchEvent->touches[i].targetY / (client_h - 1);
+        }
 
         if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) {
             SDL_SendTouch(0, deviceId, id, window_data->window, SDL_TRUE, x, y, 1.0f);
diff --git a/src/video/n3ds/SDL_n3dstouch.c b/src/video/n3ds/SDL_n3dstouch.c
index 1bc42578ee48..c0e5c07c2348 100644
--- a/src/video/n3ds/SDL_n3dstouch.c
+++ b/src/video/n3ds/SDL_n3dstouch.c
@@ -36,8 +36,8 @@
   internally in a portrait disposition so the
   GSP_SCREEN constants are flipped.
 */
-#define TOUCHSCREEN_SCALE_X 1.0f / GSP_SCREEN_HEIGHT_BOTTOM
-#define TOUCHSCREEN_SCALE_Y 1.0f / GSP_SCREEN_WIDTH
+#define TOUCHSCREEN_SCALE_X 1.0f / (GSP_SCREEN_HEIGHT_BOTTOM - 1)
+#define TOUCHSCREEN_SCALE_Y 1.0f / (GSP_SCREEN_WIDTH - 1)
 
 void N3DS_InitTouch(void)
 {
diff --git a/src/video/vita/SDL_vitatouch.c b/src/video/vita/SDL_vitatouch.c
index bdc041a3a2ac..cef1ffc9cf76 100644
--- a/src/video/vita/SDL_vitatouch.c
+++ b/src/video/vita/SDL_vitatouch.c
@@ -173,8 +173,18 @@ void VITA_PollTouch(void)
 
 void VITA_ConvertTouchXYToSDLXY(float *sdl_x, float *sdl_y, int vita_x, int vita_y, int port)
 {
-    float x = (vita_x - area_info[port].x) / area_info[port].w;
-    float y = (vita_y - area_info[port].y) / area_info[port].h;
+    float x, y;
+
+    if (area_info[port].w <= 1) {
+        x = 0.5f;
+    } else {
+        x = (vita_x - area_info[port].x) / (area_info[port].w - 1);
+    }
+    if (area_info[port].h <= 1) {
+        y = 0.5f;
+    } else {
+        y = (vita_y - area_info[port].y) / (area_info[port].h - 1);
+    }
 
     x = SDL_max(x, 0.0);
     x = SDL_min(x, 1.0);
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 2e9583cc7b76..e5b0e0a833bc 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -953,8 +953,18 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
     window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
 
     if (window_data) {
-        const float x = wl_fixed_to_double(fx) / window_data->wl_window_width;
-        const float y = wl_fixed_to_double(fy) / window_data->wl_window_height;
+        float x, y;
+
+        if (window_data->wl_window_width <= 1) {
+            x = 0.5f;
+        } else {
+            x = wl_fixed_to_double(fx) / (window_data->wl_window_width - 1);
+        }
+        if (window_data->wl_window_height <= 1) {
+            y = 0.5f;
+        } else {
+            y = wl_fixed_to_double(fy) / (window_data->wl_window_height - 1);
+        }
 
         SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
                       (SDL_FingerID)id, window_data->sdlwindow, SDL_TRUE, x, y, 1.0f);
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 042667c84b44..41d94182225f 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -1419,6 +1419,8 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 
                 for (i = 0; i < num_inputs; ++i) {
                     PTOUCHINPUT input = &inputs[i];
+                    const int w = (rect.right - rect.left);
+                    const int h = (rect.right - rect.left);
 
                     const SDL_TouchID touchId = (SDL_TouchID)((size_t)input->hSource);
 
@@ -1430,8 +1432,16 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
                     }
 
                     /* Get the normalized coordinates for the window */
-                    x = (float)(input->x - rect.left) / (rect.right - rect.left);
-                    y = (float)(input->y - rect.top) / (rect.bottom - rect.top);
+                    if (w <= 1) {
+                        x = 0.5f;
+                    } else {
+                        x = (float)(input->x - rect.left) / (w - 1);
+                    }
+                    if (h <= 1) {
+                        y = 0.5f;
+                    } else {
+                        y = (float)(input->y - rect.top) / (h - 1);
+                    }
 
                     /* FIXME: Should we use the input->dwTime field for the tick source of the timestamp? */
                     if (input->dwFlags & TOUCHEVENTF_DOWN) {