SDL: Added SDL_GetWindowSafeArea()

From 457ca3995c9929cac3a3a617cdc4145d010d6a1c Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 22 Jul 2024 18:59:53 -0700
Subject: [PATCH] Added SDL_GetWindowSafeArea()

Fixes https://github.com/libsdl-org/SDL/issues/3243
---
 .../main/java/org/libsdl/app/SDLActivity.java |  2 +
 .../main/java/org/libsdl/app/SDLSurface.java  | 24 +++++++++++-
 include/SDL3/SDL_events.h                     |  1 +
 include/SDL3/SDL_video.h                      | 14 +++++++
 src/core/android/SDL_android.c                | 18 +++++++++
 src/dynapi/SDL_dynapi.sym                     |  1 +
 src/dynapi/SDL_dynapi_overrides.h             |  1 +
 src/dynapi/SDL_dynapi_procs.h                 |  1 +
 src/events/SDL_events.c                       |  1 +
 src/events/SDL_windowevents.c                 |  1 +
 src/test/SDL_test_common.c                    |  8 ++++
 src/video/SDL_sysvideo.h                      |  7 ++++
 src/video/SDL_video.c                         | 39 +++++++++++++++++++
 src/video/cocoa/SDL_cocoawindow.m             | 16 ++++++++
 src/video/uikit/SDL_uikitview.h               |  2 +
 src/video/uikit/SDL_uikitview.m               | 12 ++++++
 16 files changed, 147 insertions(+), 1 deletion(-)

diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
index f2b4f2c516783..a27ce19f2e1ab 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -918,6 +918,7 @@ public void handleMessage(Message msg) {
                                 window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
                                 SDLActivity.mFullscreenModeActive = false;
                             }
+                            window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
                         }
                     } else {
                         Log.e(TAG, "error handling message, getContext() returned no Activity");
@@ -1057,6 +1058,7 @@ public static native void onNativeTouch(int touchDevId, int pointerFingerId,
     public static native void nativeSetenv(String name, String value);
     public static native void nativeSetNaturalOrientation(int orientation);
     public static native void onNativeRotationChanged(int rotation);
+    public static native void onNativeInsetsChanged(int left, int right, int top, int bottom);
     public static native void nativeAddTouch(int touchId, String name);
     public static native void nativePermissionResult(int requestCode, boolean result);
     public static native void onNativeLocaleChanged();
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 74ecaf5bf42e1..0617757465953 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
@@ -3,6 +3,7 @@
 
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.graphics.Insets;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
@@ -18,6 +19,7 @@
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 
 
@@ -28,7 +30,7 @@
     Because of this, that's where we set up the SDL thread
 */
 public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
-    View.OnKeyListener, View.OnTouchListener, SensorEventListener  {
+    View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener, SensorEventListener  {
 
     // Sensors
     protected SensorManager mSensorManager;
@@ -48,6 +50,7 @@ public SDLSurface(Context context) {
         setFocusable(true);
         setFocusableInTouchMode(true);
         requestFocus();
+        setOnApplyWindowInsetsListener(this);
         setOnKeyListener(this);
         setOnTouchListener(this);
 
@@ -71,6 +74,7 @@ public void handleResume() {
         setFocusable(true);
         setFocusableInTouchMode(true);
         requestFocus();
+        setOnApplyWindowInsetsListener(this);
         setOnKeyListener(this);
         setOnTouchListener(this);
         enableSensor(Sensor.TYPE_ACCELEROMETER, true);
@@ -187,6 +191,24 @@ public void surfaceChanged(SurfaceHolder holder,
         SDLActivity.handleNativeState();
     }
 
+    // Window inset
+    @Override
+    public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+        Insets combined = insets.getInsets(WindowInsets.Type.statusBars() |
+                                           WindowInsets.Type.navigationBars() |
+                                           WindowInsets.Type.captionBar() |
+                                           WindowInsets.Type.systemGestures() |
+                                           WindowInsets.Type.mandatorySystemGestures() |
+                                           WindowInsets.Type.tappableElement() |
+                                           WindowInsets.Type.displayCutout() |
+                                           WindowInsets.Type.systemOverlays());
+
+        SDLActivity.onNativeInsetsChanged(combined.left, combined.right, combined.top, combined.bottom);
+
+        // Pass these to any child views in case they need them
+        return insets;
+    }
+
     // Key events
     @Override
     public boolean onKey(View v, int keyCode, KeyEvent event) {
diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index 280c2a4a1dc49..58467d3aec3e7 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -142,6 +142,7 @@ typedef enum SDL_EventType
     SDL_EVENT_WINDOW_ICCPROF_CHANGED,   /**< The ICC profile of the window's display has changed */
     SDL_EVENT_WINDOW_DISPLAY_CHANGED,   /**< Window has been moved to display data1 */
     SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED, /**< Window display scale has been changed */
+    SDL_EVENT_WINDOW_SAFE_AREA_CHANGED, /**< The window safe area has been changed */
     SDL_EVENT_WINDOW_OCCLUDED,          /**< The window has been occluded */
     SDL_EVENT_WINDOW_ENTER_FULLSCREEN,  /**< The window has entered fullscreen mode */
     SDL_EVENT_WINDOW_LEAVE_FULLSCREEN,  /**< The window has left fullscreen mode */
diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h
index 2d3769ef99c70..3659c63ea95f0 100644
--- a/include/SDL3/SDL_video.h
+++ b/include/SDL3/SDL_video.h
@@ -1480,6 +1480,20 @@ extern SDL_DECLSPEC int SDLCALL SDL_SetWindowSize(SDL_Window *window, int w, int
  */
 extern SDL_DECLSPEC int SDLCALL SDL_GetWindowSize(SDL_Window *window, int *w, int *h);
 
+/**
+ * Get the safe area for this window.
+ *
+ * Some devices have portions of the screen which are partially obscured or not interactive, possibly due to on-screen controls, curved edges, camera notches, TV overscan, etc. This function provides the area of the window which is safe to have interactible content. You should continue rendering into the rest of the window, but it should not contain visually important or interactible content.
+ *
+ * \param window the window to query.
+ * \param rect a pointer filled in with the client area that is safe for interactive content.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetWindowSafeArea(SDL_Window *window, SDL_Rect *rect);
+
 /**
  * Request that the aspect ratio of a window's client area be set.
  *
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index 6f5e5ec6a8a3a..3e1b8b4f08efe 100644
--- a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -163,6 +163,10 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeRotationChanged)(
     JNIEnv *env, jclass cls,
     jint rotation);
 
+JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)(
+    JNIEnv *env, jclass cls,
+    jint left, jint right, jint top, jint bottom);
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)(
     JNIEnv *env, jclass cls,
     jint touchId, jstring name);
@@ -212,6 +216,7 @@ static JNINativeMethod SDLActivity_tab[] = {
     { "nativeSetenv", "(Ljava/lang/String;Ljava/lang/String;)V", SDL_JAVA_INTERFACE(nativeSetenv) },
     { "nativeSetNaturalOrientation", "(I)V", SDL_JAVA_INTERFACE(nativeSetNaturalOrientation) },
     { "onNativeRotationChanged", "(I)V", SDL_JAVA_INTERFACE(onNativeRotationChanged) },
+    { "onNativeInsetsChanged", "(IIII)V", SDL_JAVA_INTERFACE(onNativeInsetsChanged) },
     { "nativeAddTouch", "(ILjava/lang/String;)V", SDL_JAVA_INTERFACE(nativeAddTouch) },
     { "nativePermissionResult", "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) },
     { "nativeAllowRecreateActivity", "()Z", SDL_JAVA_INTERFACE(nativeAllowRecreateActivity) },
@@ -997,6 +1002,19 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeRotationChanged)(
     SDL_UnlockMutex(Android_ActivityMutex);
 }
 
+JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)(
+    JNIEnv *env, jclass jcls,
+    jint left, jint right, jint top, jint bottom)
+{
+    SDL_LockMutex(Android_ActivityMutex);
+
+    if (Android_Window) {
+        SDL_SetWindowSafeAreaInsets(Android_Window, left, right, top, bottom);
+    }
+
+    SDL_UnlockMutex(Android_ActivityMutex);
+}
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)(
     JNIEnv *env, jclass cls,
     jint touchId, jstring name)
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 5b3b825566755..2f3013f3bcda8 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -484,6 +484,7 @@ SDL3_0.0.0 {
     SDL_GetWindowPixelFormat;
     SDL_GetWindowPosition;
     SDL_GetWindowProperties;
+    SDL_GetWindowSafeArea;
     SDL_GetWindowSize;
     SDL_GetWindowSizeInPixels;
     SDL_GetWindowSurface;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 22d210e9f0245..7318561b321d0 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -509,6 +509,7 @@
 #define SDL_GetWindowPixelFormat SDL_GetWindowPixelFormat_REAL
 #define SDL_GetWindowPosition SDL_GetWindowPosition_REAL
 #define SDL_GetWindowProperties SDL_GetWindowProperties_REAL
+#define SDL_GetWindowSafeArea SDL_GetWindowSafeArea_REAL
 #define SDL_GetWindowSize SDL_GetWindowSize_REAL
 #define SDL_GetWindowSizeInPixels SDL_GetWindowSizeInPixels_REAL
 #define SDL_GetWindowSurface SDL_GetWindowSurface_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index c7264154bf82d..75d40beac62c4 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -529,6 +529,7 @@ SDL_DYNAPI_PROC(float,SDL_GetWindowPixelDensity,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(SDL_PixelFormat,SDL_GetWindowPixelFormat,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetWindowPosition,(SDL_Window *a, int *b, int *c),(a,b,c),return)
 SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetWindowProperties,(SDL_Window *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_GetWindowSafeArea,(SDL_Window *a, SDL_Rect *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_GetWindowSize,(SDL_Window *a, int *b, int *c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_GetWindowSizeInPixels,(SDL_Window *a, int *b, int *c),(a,b,c),return)
 SDL_DYNAPI_PROC(SDL_Surface*,SDL_GetWindowSurface,(SDL_Window *a),(a),return)
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index d166ced93b8bc..5cecaa3d338fc 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -497,6 +497,7 @@ static void SDL_LogEvent(const SDL_Event *event)
         SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MOVED);
         SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_RESIZED);
         SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED);
+        SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_SAFE_AREA_CHANGED);
         SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MINIMIZED);
         SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MAXIMIZED);
         SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_RESTORED);
diff --git a/src/events/SDL_windowevents.c b/src/events/SDL_windowevents.c
index 0273b2c61fbab..80fd511c151be 100644
--- a/src/events/SDL_windowevents.c
+++ b/src/events/SDL_windowevents.c
@@ -193,6 +193,7 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent,
         if (windowevent == SDL_EVENT_WINDOW_MOVED ||
             windowevent == SDL_EVENT_WINDOW_RESIZED ||
             windowevent == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ||
+            windowevent == SDL_EVENT_WINDOW_SAFE_AREA_CHANGED ||
             windowevent == SDL_EVENT_WINDOW_EXPOSED ||
             windowevent == SDL_EVENT_WINDOW_OCCLUDED) {
             SDL_FilterEvents(RemoveSupercededWindowEvents, &event);
diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c
index 2de6b74c98853..2f12f8cd0b101 100644
--- a/src/test/SDL_test_common.c
+++ b/src/test/SDL_test_common.c
@@ -1614,6 +1614,14 @@ static void SDLTest_PrintEvent(const SDL_Event *event)
         SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " changed pixel size to %" SDL_PRIs32 "x%" SDL_PRIs32,
                 event->window.windowID, event->window.data1, event->window.data2);
         break;
+    case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: {
+        SDL_Rect rect;
+
+        SDL_GetWindowSafeArea(SDL_GetWindowFromID(event->window.windowID), &rect);
+        SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " changed safe area to: %d,%d %dx%d\n", 
+                event->window.windowID, rect.x, rect.y, rect.w, rect.h);
+        break;
+    }
     case SDL_EVENT_WINDOW_MINIMIZED:
         SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " minimized", event->window.windowID);
         break;
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index ec2eaba606df3..2be73c9b5614f 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -100,6 +100,12 @@ struct SDL_Window
     SDL_bool is_destroying;
     SDL_bool is_dropping; /* drag/drop in progress, expecting SDL_SendDropComplete(). */
 
+    int safe_inset_left;
+    int safe_inset_right;
+    int safe_inset_top;
+    int safe_inset_bottom;
+    SDL_Rect safe_rect;
+
     SDL_bool text_input_active;
     SDL_Rect text_input_rect;
     int text_input_cursor;
@@ -526,6 +532,7 @@ extern SDL_DisplayData *SDL_GetDisplayDriverData(SDL_DisplayID display);
 extern SDL_DisplayData *SDL_GetDisplayDriverDataForWindow(SDL_Window *window);
 extern int SDL_GetMessageBoxCount(void);
 extern void SDL_SetWindowHDRProperties(SDL_Window *window, const SDL_HDROutputProperties *HDR, SDL_bool send_event);
+extern void SDL_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, int top, int bottom);
 
 extern void SDL_GL_DeduceMaxSupportedESProfile(int *major, int *minor);
 
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 5f0caba9b52e2..0f302b6b82e1c 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -165,6 +165,7 @@ extern SDL_bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, SDL_bool stat
 
 static void SDL_CheckWindowDisplayChanged(SDL_Window *window);
 static void SDL_CheckWindowDisplayScaleChanged(SDL_Window *window);
+static void SDL_CheckWindowSafeAreaChanged(SDL_Window *window);
 
 /* Convenience functions for reading driver flags */
 static SDL_bool SDL_ModeSwitchingEmulated(SDL_VideoDevice *_this)
@@ -3789,6 +3790,7 @@ void SDL_OnWindowResized(SDL_Window *window)
 {
     SDL_CheckWindowDisplayChanged(window);
     SDL_CheckWindowPixelSizeChanged(window);
+    SDL_CheckWindowSafeAreaChanged(window);
 
     if ((window->flags & SDL_WINDOW_TRANSPARENT) && _this->UpdateWindowShape) {
         SDL_Surface *surface = (SDL_Surface *)SDL_GetPointerProperty(window->props, SDL_PROP_WINDOW_SHAPE_POINTER, NULL);
@@ -3813,6 +3815,43 @@ void SDL_OnWindowPixelSizeChanged(SDL_Window *window)
     window->surface_valid = SDL_FALSE;
 }
 
+static void SDL_CheckWindowSafeAreaChanged(SDL_Window *window)
+{
+    SDL_Rect rect;
+
+    rect.x = window->safe_inset_left;
+    rect.y = window->safe_inset_top;
+    rect.w = window->w - (window->safe_inset_right + window->safe_inset_left);
+    rect.h = window->h - (window->safe_inset_top + window->safe_inset_bottom);
+    if (SDL_memcmp(&rect, &window->safe_rect, sizeof(rect)) != 0) {
+        SDL_copyp(&window->safe_rect, &rect);
+        SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SAFE_AREA_CHANGED, 0, 0);
+    }
+}
+
+void SDL_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, int top, int bottom)
+{
+    window->safe_inset_left = left;
+    window->safe_inset_right = right;
+    window->safe_inset_top = top;
+    window->safe_inset_bottom = bottom;
+    SDL_CheckWindowSafeAreaChanged(window);
+}
+
+int SDL_GetWindowSafeArea(SDL_Window *window, SDL_Rect *rect)
+{
+    if (rect) {
+        SDL_zerop(rect);
+    }
+
+    CHECK_WINDOW_MAGIC(window, -1);
+
+    if (rect) {
+        SDL_copyp(rect, &window->safe_rect);
+    }
+    return 0;
+}
+
 void SDL_OnWindowMinimized(SDL_Window *window)
 {
     if (window->flags & SDL_WINDOW_FULLSCREEN) {
diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m
index 7b42c49443669..eb5a50fbab2ee 100644
--- a/src/video/cocoa/SDL_cocoawindow.m
+++ b/src/video/cocoa/SDL_cocoawindow.m
@@ -2785,6 +2785,22 @@ int Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_Vi
             [data.listener resumeVisibleObservation];
         }
 
+        // Update the safe area insets
+        // The view never seems to reflect the safe area, so we'll use the screen instead
+        if (@available(macOS 12.0, *)) {
+            if (fullscreen) {
+                NSScreen *screen = [nswindow screen];
+
+                SDL_SetWindowSafeAreaInsets(data.window,
+                                            (int)SDL_ceilf(screen.safeAreaInsets.left),
+                                            (int)SDL_ceilf(screen.safeAreaInsets.right),
+                                            (int)SDL_ceilf(screen.safeAreaInsets.top),
+                                            (int)SDL_ceilf(screen.safeAreaInsets.bottom));
+            } else {
+                SDL_SetWindowSafeAreaInsets(data.window, 0, 0, 0, 0);
+            }
+        }
+
         ScheduleContextUpdates(data);
     }
 
diff --git a/src/video/uikit/SDL_uikitview.h b/src/video/uikit/SDL_uikitview.h
index 0d720da720d21..20375d0f8e2db 100644
--- a/src/video/uikit/SDL_uikitview.h
+++ b/src/video/uikit/SDL_uikitview.h
@@ -43,4 +43,6 @@
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
 
+- (void)safeAreaInsetsDidChange;
+
 @end
diff --git a/src/video/uikit/SDL_uikitview.m b/src/video/uikit/SDL_uikitview.m
index 4f0c1f4d5c3c5..b01714535f329 100644
--- a/src/video/uikit/SDL_uikitview.m
+++ b/src/video/uikit/SDL_uikitview.m
@@ -368,6 +368,18 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
     }
 }
 
+- (void)safeAreaInsetsDidChange
+{
+    // Update the safe area insets
+    if (@available(iOS 11.0, tvOS 11.0, *)) {
+        SDL_SetWindowSafeAreaInsets(sdlwindow,
+                                    (int)SDL_ceilf(self.safeAreaInsets.left),
+                                    (int)SDL_ceilf(self.safeAreaInsets.right),
+                                    (int)SDL_ceilf(self.safeAreaInsets.top),
+                                    (int)SDL_ceilf(self.safeAreaInsets.bottom));
+    }
+}
+
 #if defined(SDL_PLATFORM_TVOS) || defined(__IPHONE_9_1)
 - (SDL_Scancode)scancodeFromPress:(UIPress *)press
 {

Is SDL_GetWindowSafeArea() going to be added to SDL2? Those of us who cannot migrate to SDL3 (in my case partly because of the lack of support for a GLES 1.1 backend) are going to need this, or something similar, to support Android edge-to-edge, which is required for Android 16 (API level 36) and is likely to be enforced by the Play Store quite soon.

It’s also not clear to me what units the safe client area is returned in when SDL_WINDOW_ALLOW_HIGHDPI is active. Is it physical units (pixels, as with SDL_GL_GetDrawableSize()) or logical units (as with SDL_GetWindowSize())?

There are no plans to add that API to SDL2, though if someone wants to create a backport PR, we can consider it.

The units are in logical units, like all other window APIs.