From 6e4731b5e8c2b9e7efeb9120b96ed096da1248ed Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Sat, 5 Sep 2026 12:15:19 +0200 Subject: [PATCH] Identify handheld iOS device families --- README.md | 10 ++++++++++ .../libjglios/core/LibJGLIOSDeviceBridge.java | 3 +++ .../main/native/libjglios_ios_launcher_jni.mm | 19 +++++++++++++++++++ .../core/LibJGLIOSDeviceBridgeTest.java | 18 ++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 core-ios/src/test/java/org/ngengine/libjglios/core/LibJGLIOSDeviceBridgeTest.java diff --git a/README.md b/README.md index 673e12f..49fedad 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,16 @@ It includes bindings for SDL3, OpenAL, Clipboard and OpenGL ES via ANGLE on Meta - `:angle-ios`: native build of Google ANGLE for iOS - `:libjglios-gradle-plugin`: `org.ngengine.libjglios`, a Gradle plugin to build iOS apps +## Device classification + +`LibJGLIOSDeviceBridge.isMobileDevice()` identifies phone and iPad device families +through UIKit. It returns false for other interface idioms and for iOS apps +running on Mac. The native call marshals UIKit access to the main thread, so a +caller on another thread requires the application's main run loop to be running. +This describes the device family, not whether a gamepad is connected. + +Consumers need matching Java and native core artifacts containing this method. + ## Usage Consumer projects apply: diff --git a/core-ios/src/main/java/org/ngengine/libjglios/core/LibJGLIOSDeviceBridge.java b/core-ios/src/main/java/org/ngengine/libjglios/core/LibJGLIOSDeviceBridge.java index 7961e46..c5e6f8e 100644 --- a/core-ios/src/main/java/org/ngengine/libjglios/core/LibJGLIOSDeviceBridge.java +++ b/core-ios/src/main/java/org/ngengine/libjglios/core/LibJGLIOSDeviceBridge.java @@ -4,6 +4,9 @@ public final class LibJGLIOSDeviceBridge { private LibJGLIOSDeviceBridge() { } + /** Returns true for the phone and tablet device families, not TV or Mac. */ + public static native boolean isMobileDevice(); + public static native boolean isRumbleSupported(); public static native void rumble(float amountHigh, float amountLow, float duration); diff --git a/core-ios/src/main/native/libjglios_ios_launcher_jni.mm b/core-ios/src/main/native/libjglios_ios_launcher_jni.mm index 881455f..4b35a15 100644 --- a/core-ios/src/main/native/libjglios_ios_launcher_jni.mm +++ b/core-ios/src/main/native/libjglios_ios_launcher_jni.mm @@ -294,6 +294,25 @@ void libjglios_show_error_alert(const char* title, const char* message) { return JNI_TRUE; } +extern "C" JNIEXPORT jboolean JNICALL +Java_org_ngengine_libjglios_core_LibJGLIOSDeviceBridge_isMobileDevice(JNIEnv*, jclass) { + __block bool mobile = false; + void (^classify)(void) = ^{ + UIUserInterfaceIdiom idiom = UIDevice.currentDevice.userInterfaceIdiom; + mobile = idiom == UIUserInterfaceIdiomPhone || idiom == UIUserInterfaceIdiomPad; + if (@available(iOS 14.0, *)) { + // iPhone/iPad apps running on Apple Silicon Macs can retain their original idiom. + mobile = mobile && !NSProcessInfo.processInfo.iOSAppOnMac; + } + }; + if ([NSThread isMainThread]) { + classify(); + } else { + dispatch_sync(dispatch_get_main_queue(), classify); + } + return mobile ? JNI_TRUE : JNI_FALSE; +} + extern "C" JNIEXPORT jboolean JNICALL Java_org_ngengine_libjglios_core_LibJGLIOSDeviceBridge_isRumbleSupported(JNIEnv*, jclass) { return libjglios_device_rumble_supported() ? JNI_TRUE : JNI_FALSE; diff --git a/core-ios/src/test/java/org/ngengine/libjglios/core/LibJGLIOSDeviceBridgeTest.java b/core-ios/src/test/java/org/ngengine/libjglios/core/LibJGLIOSDeviceBridgeTest.java new file mode 100644 index 0000000..341bd64 --- /dev/null +++ b/core-ios/src/test/java/org/ngengine/libjglios/core/LibJGLIOSDeviceBridgeTest.java @@ -0,0 +1,18 @@ +package org.ngengine.libjglios.core; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class LibJGLIOSDeviceBridgeTest { + @Test + void exposesNativeMobileClassification() throws Exception { + Method method = LibJGLIOSDeviceBridge.class.getDeclaredMethod("isMobileDevice"); + assertEquals(boolean.class, method.getReturnType()); + assertTrue(Modifier.isPublic(method.getModifiers())); + assertTrue(Modifier.isStatic(method.getModifiers())); + assertTrue(Modifier.isNative(method.getModifiers())); + } +}