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 85d51541c219f..c7689a28754af 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 @@ -1303,6 +1303,25 @@ public static boolean isVRHeadset() { return false; } + /** + * This method is called by SDL using JNI. + */ + static String getDeviceFormFactor() + { + // TODO: WearOS + if (isAndroidTV()) { + return "tv"; + } else if (isVRHeadset()) { + return "vr"; + } else if (isTablet()) { + return "tablet"; + //} else if (isAndroidAutomotive()) { + // return "car"; + } else { + return "phone"; + } + } + public static double getDiagonal() { DisplayMetrics metrics = new DisplayMetrics(); diff --git a/include/SDL3/SDL_system.h b/include/SDL3/SDL_system.h index 294089ff4a337..fbb8865eee14a 100644 --- a/include/SDL3/SDL_system.h +++ b/include/SDL3/SDL_system.h @@ -620,6 +620,46 @@ extern SDL_DECLSPEC bool SDLCALL SDL_IsTablet(void); */ extern SDL_DECLSPEC bool SDLCALL SDL_IsTV(void); +/** + * The possible form factors for a device. + * + * \since This enum is available since SDL 3.4.0. + * + * \sa SDL_GetDeviceFormFactor + */ +typedef enum SDL_FormFactor { + SDL_FORMFACTOR_UNKNOWN = 0, + SDL_FORMFACTOR_DESKTOP, + SDL_FORMFACTOR_LAPTOP, + SDL_FORMFACTOR_PHONE, + SDL_FORMFACTOR_TABLET, + SDL_FORMFACTOR_CONSOLE, + SDL_FORMFACTOR_HANDHELD, + SDL_FORMFACTOR_WATCH, + SDL_FORMFACTOR_TV, + SDL_FORMFACTOR_VR, + SDL_FORMFACTOR_CAR +} SDL_FormFactor; + +/** + * Get the form factor of the current device. + * + * This function guesses what the device may be, but may report inaccurate or + * outright wrong results. For example, it may report a laptop as a desktop, or + * a car device as a phone. + * + * Depending on the usage, there may be different functions better suited for + * each purpose. For example, activating touch controls can be done by detecting + * the presence of a touchscreen rather than restricting to phones and tablets. + * + * \returns the best guess for the form factor of the current device. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_FormFactor + */ +extern SDL_DECLSPEC SDL_FormFactor SDLCALL SDL_GetDeviceFormFactor(void); + /** * Application sandbox environment. * diff --git a/src/SDL.c b/src/SDL.c index 502f6617a4f26..feeb34cd18459 100644 --- a/src/SDL.c +++ b/src/SDL.c @@ -769,25 +769,36 @@ const char *SDL_GetPlatform(void) bool SDL_IsTablet(void) { -#ifdef SDL_PLATFORM_ANDROID - return SDL_IsAndroidTablet(); -#elif defined(SDL_PLATFORM_IOS) - extern bool SDL_IsIPad(void); - return SDL_IsIPad(); -#else - return false; -#endif + return SDL_GetDeviceFormFactor() == SDL_FORMFACTOR_TABLET; } bool SDL_IsTV(void) { + return SDL_GetDeviceFormFactor() == SDL_FORMFACTOR_TV; +} + +SDL_FormFactor SDL_GetDeviceFormFactor(void) +{ + /* TODO: SDL private platforms? */ #ifdef SDL_PLATFORM_ANDROID - return SDL_IsAndroidTV(); + return SDL_GetAndroidDeviceFormFactor(); #elif defined(SDL_PLATFORM_IOS) - extern bool SDL_IsAppleTV(void); - return SDL_IsAppleTV(); + extern bool SDL_GetUIKitDeviceFormFactor(void); + return SDL_GetUIKitDeviceFormFactor(); +#elif defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) || defined(SDL_PLATFORM_PS2) + return SDL_FORMFACTOR_CONSOLE; +#elif defined(SDL_PLATFORM_PSP) || defined(SDL_PLATFORM_VITA) || defined(SDL_PLATFORM_3DS) + return SDL_FORMFACTOR_HANDHELD; +#elif defined(SDL_PLATFORM_QNXNTO) + /* TODO: QNX is used in BlackBerry phones and tablets, and in many embedded devices */ + return SDL_FORMFACTOR_UNKNOWN; +#elif defined(SDL_PLATFORM_WINGDK) + /* TODO: GDK can be either desktop Windows or XBox */ + return SDL_FORMFACTOR_UNKNOWN; +#elif defined(SDL_PLATFORM_HAIKU) || defined(SDL_PLATFORM_BSDI) || defined(SDL_PLATFORM_HPUX) || defined(SDL_PLATFORM_IRIX) || defined(SDL_PLATFORM_EMSCRIPTEN) || defined(SDL_PLATFORM_OS2) || defined(SDL_PLATFORM_OSF) || defined(SDL_PLATFORM_RISCOS) || defined(SDL_PLATFORM_SOLARIS) || defined(SDL_PLATFORM_WIN32) + return SDL_FORMFACTOR_DESKTOP; #else - return false; + return SDL_FORMFACTOR_UNKNOWN; #endif } diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 1fb6fbf65b0a5..020b3a10d05cb 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -346,6 +346,7 @@ static jmethodID midClipboardSetText; static jmethodID midCreateCustomCursor; static jmethodID midDestroyCustomCursor; static jmethodID midGetContext; +static jmethodID midGetDeviceFormFactor; static jmethodID midGetManifestEnvironmentVariables; static jmethodID midGetNativeSurface; static jmethodID midInitTouch; @@ -635,6 +636,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl midCreateCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "createCustomCursor", "([IIIII)I"); midDestroyCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "destroyCustomCursor", "(I)V"); midGetContext = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;"); + midGetDeviceFormFactor = (*env)->GetStaticMethodID(env, mActivityClass, "getDeviceFormFactor", "()Ljava/lang/String;"); midGetManifestEnvironmentVariables = (*env)->GetStaticMethodID(env, mActivityClass, "getManifestEnvironmentVariables", "()Z"); midGetNativeSurface = (*env)->GetStaticMethodID(env, mActivityClass, "getNativeSurface", "()Landroid/view/Surface;"); midInitTouch = (*env)->GetStaticMethodID(env, mActivityClass, "initTouch", "()V"); @@ -667,6 +669,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl !midCreateCustomCursor || !midDestroyCustomCursor || !midGetContext || + !midGetDeviceFormFactor || !midGetManifestEnvironmentVariables || !midGetNativeSurface || !midInitTouch || @@ -2821,4 +2824,37 @@ bool Android_JNI_OpenFileDialog( return true; } +SDL_FormFactor SDL_GetAndroidDeviceFormFactor(void) +{ + JNIEnv *env = Android_JNI_GetEnv(); + SDL_FormFactor form_factor = SDL_FORMFACTOR_UNKNOWN; + jstring string; + + string = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetDeviceFormFactor); + if (string) { + const char *utf = (*env)->GetStringUTFChars(env, string, 0); + if (utf) { + if (SDL_strcmp(utf, "tv") == 0) { + form_factor = SDL_FORMFACTOR_TV; + } else if (SDL_strcmp(utf, "tablet") == 0) { + form_factor = SDL_FORMFACTOR_TABLET; + } else if (SDL_strcmp(utf, "phone") == 0) { + form_factor = SDL_FORMFACTOR_PHONE; + } else if (SDL_strcmp(utf, "car") == 0) { + form_factor = SDL_FORMFACTOR_CAR; + } else if (SDL_strcmp(utf, "vr") == 0) { + form_factor = SDL_FORMFACTOR_VR; + } else if (SDL_strcmp(utf, "watch") == 0) { + form_factor = SDL_FORMFACTOR_WATCH; + } else { + form_factor = SDL_FORMFACTOR_UNKNOWN; + } + (*env)->ReleaseStringUTFChars(env, string, utf); + } + (*env)->DeleteLocalRef(env, string); + } + + return form_factor; +} + #endif // SDL_PLATFORM_ANDROID diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index 3541c2a9ccc92..433a483268a49 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -147,6 +147,7 @@ int SDL_GetAndroidSDKVersion(void); bool SDL_IsAndroidTablet(void); bool SDL_IsAndroidTV(void); +SDL_FormFactor SDL_GetAndroidDeviceFormFactor(void); // File Dialogs bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void* userdata, diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 9f84fbbc91fdf..c7bbbd366411c 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1246,6 +1246,7 @@ SDL3_0.0.0 { SDL_SetWindowProgressValue; SDL_GetWindowProgressState; SDL_GetWindowProgressValue; + SDL_GetDeviceFormFactor; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 7ebb71721cae3..7314296fbc91e 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1271,3 +1271,4 @@ #define SDL_SetWindowProgressValue SDL_SetWindowProgressValue_REAL #define SDL_GetWindowProgressState SDL_GetWindowProgressState_REAL #define SDL_GetWindowProgressValue SDL_GetWindowProgressValue_REAL +#define SDL_GetDeviceFormFactor SDL_GetDeviceFormFactor_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index fa12a5815aca5..b2f20fcb3706b 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1279,3 +1279,4 @@ SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressState,(SDL_Window *a,SDL_ProgressState SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressValue,(SDL_Window *a,float b),(a,b),return) SDL_DYNAPI_PROC(SDL_ProgressState,SDL_GetWindowProgressState,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(float,SDL_GetWindowProgressValue,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(SDL_FormFactor,SDL_GetDeviceFormFactor,(void),(),return) diff --git a/src/video/uikit/SDL_uikitvideo.m b/src/video/uikit/SDL_uikitvideo.m index f5326d83862ca..bb7bbfb277aff 100644 --- a/src/video/uikit/SDL_uikitvideo.m +++ b/src/video/uikit/SDL_uikitvideo.m @@ -301,14 +301,26 @@ void SDL_NSLog(const char *prefix, const char *text) * This doesn't really have anything to do with the interfaces of the SDL video * subsystem, but we need to stuff this into an Objective-C source code file. */ -bool SDL_IsIPad(void) -{ - return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad); -} -bool SDL_IsAppleTV(void) +bool SDL_GetUIKitDeviceFormFactor(void) { - return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomTV); + // TODO: Apple Watch + switch ([UIDevice currentDevice].userInterfaceIdiom) { + case UIUserInterfaceIdiomPhone: + return SDL_FORMFACTOR_PHONE; + case UIUserInterfaceIdiomPad: + return SDL_FORMFACTOR_TABLET; + case UIUserInterfaceIdiomTV: + return SDL_FORMFACTOR_TV; + case UIUserInterfaceIdiomCarPlay: + return SDL_FORMFACTOR_CAR; + case UIUserInterfaceIdiomMac: + return SDL_FORMFACTOR_DESKTOP; + case UIUserInterfaceIdiomVision: + return SDL_FORMFACTOR_VR; + default: + return SDL_FORMFACTOR_UNKNOWN; + } } #endif // SDL_VIDEO_DRIVER_UIKIT