From b8601dc8a7e41bc747d0950713dfbf672cb8d8e7 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 27 May 2026 15:41:51 -0400
Subject: [PATCH] filesystem: Implement SDL_GetExeName() for all platforms.
(cherry picked from commit d7ba3efe6bd80c5c49911798649b698a96999e7f)
---
src/SDL_internal.h | 4 ++-
src/core/android/SDL_android.c | 37 ++++++++++++++++++++++
src/core/android/SDL_android.h | 2 ++
src/filesystem/android/SDL_sysfilesystem.c | 5 ++-
src/filesystem/cocoa/SDL_sysfilesystem.m | 22 +++++++++++--
src/filesystem/haiku/SDL_sysfilesystem.cc | 9 ++++--
src/filesystem/ngage/SDL_sysfilesystem.c | 6 ++--
src/filesystem/ngage/SDL_sysfilesystem.cpp | 17 ++++++++++
src/filesystem/riscos/SDL_sysfilesystem.c | 21 ++++++++++--
test/testfilesystem.c | 3 ++
10 files changed, 113 insertions(+), 13 deletions(-)
diff --git a/src/SDL_internal.h b/src/SDL_internal.h
index b24930984c6be..475009366e51c 100644
--- a/src/SDL_internal.h
+++ b/src/SDL_internal.h
@@ -266,7 +266,9 @@ extern "C" {
anything calling it without an extremely good reason. */
extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
-// Get just the process's binary name, no path. Calculates and caches the string on first call. String lives until SDL_Quit(). This is not a public API right now!
+// Get just the process's binary name, no path. NULL if it doesn't make sense for a platform.
+// Can be something not a file, like a package ID on Android. Meant to be human-readable, not appended to a path, etc.
+// Calculates and caches the string on first call. String lives until SDL_Quit(). This is not a public API right now!
extern const char *SDL_GetExeName(void);
#ifdef HAVE_LIBC
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index 3e24056bd5f66..59ea5574b71cd 100644
--- a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -2848,6 +2848,43 @@ int SDL_GetAndroidSDKVersion(void)
return sdk_version;
}
+char *SDL_GetAndroidPackageName(void)
+{
+ // this doesn't currently cache this, because it's only used by SDL_GetExeName, which _does_ cache it.
+ struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(SDL_FUNCTION);
+
+ JNIEnv *env = Android_JNI_GetEnv();
+ if (!LocalReferenceHolder_Init(&refs, env)) {
+ LocalReferenceHolder_Cleanup(&refs);
+ return NULL;
+ }
+
+ // context = SDLActivity.getContext();
+ jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
+ if (!context) {
+ SDL_SetError("Couldn't get Android context!");
+ LocalReferenceHolder_Cleanup(&refs);
+ return NULL;
+ }
+
+ // fileObj = context.getFilesDir();
+ jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), "getPackageName", "()Ljava/lang/String;");
+ jstring jstr = (jstring)(*env)->CallObjectMethod(env, context, mid);
+ if (Android_JNI_ExceptionOccurred(false)) {
+ LocalReferenceHolder_Cleanup(&refs);
+ return NULL;
+ }
+
+ const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
+ char *retval = cstr ? SDL_strdup(cstr) : NULL;
+ (*env)->ReleaseStringUTFChars(env, jstr, cstr);
+
+ LocalReferenceHolder_Cleanup(&refs);
+
+ return retval;
+}
+
+
bool SDL_IsAndroidTablet(void)
{
JNIEnv *env = Android_JNI_GetEnv();
diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h
index fa646e763d950..eedb6617577b4 100644
--- a/src/core/android/SDL_android.h
+++ b/src/core/android/SDL_android.h
@@ -153,6 +153,8 @@ int SDL_GetAndroidSDKVersion(void);
bool SDL_IsAndroidTablet(void);
bool SDL_IsAndroidTV(void);
+char *SDL_GetAndroidPackageName(void); // this is a SDL_malloc'd string the caller will own.
+
// File Dialogs
bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void *userdata,
const SDL_DialogFileFilter *filters, int nfilters, bool forwrite,
diff --git a/src/filesystem/android/SDL_sysfilesystem.c b/src/filesystem/android/SDL_sysfilesystem.c
index 3fd8afd986438..89cd0ce1edc84 100644
--- a/src/filesystem/android/SDL_sysfilesystem.c
+++ b/src/filesystem/android/SDL_sysfilesystem.c
@@ -26,8 +26,7 @@
// System dependent filesystem routines
#include "../SDL_sysfilesystem.h"
-
-#include <unistd.h>
+#include "../../core/android/SDL_android.h"
char *SDL_SYS_GetBasePath(void)
{
@@ -36,7 +35,7 @@ char *SDL_SYS_GetBasePath(void)
char *SDL_SYS_GetExeName(void)
{
- return NULL; // !!! FIXME: probably just use the Linux path?
+ return SDL_GetAndroidPackageName();
}
char *SDL_SYS_GetPrefPath(const char *org, const char *app)
diff --git a/src/filesystem/cocoa/SDL_sysfilesystem.m b/src/filesystem/cocoa/SDL_sysfilesystem.m
index 2995df4e5de03..4cd9a563c7283 100644
--- a/src/filesystem/cocoa/SDL_sysfilesystem.m
+++ b/src/filesystem/cocoa/SDL_sysfilesystem.m
@@ -47,7 +47,7 @@
} else if (SDL_strcasecmp(baseType, "parent") == 0) {
base = [[[bundle bundlePath] stringByDeletingLastPathComponent] fileSystemRepresentation];
} else {
- // this returns the exedir for non-bundled and the resourceDir for bundled apps
+ // this returns the exedir for non-bundled and the resourceDir for bundled apps
base = [[bundle resourcePath] fileSystemRepresentation];
}
@@ -65,8 +65,24 @@
char *SDL_SYS_GetExeName(void)
{
- SDL_Unsupported(); // !!! FIXME
- return NULL;
+ @autoreleasepool {
+ NSBundle *bundle = [NSBundle mainBundle];
+ const char *name = [[[bundle infoDictionary] objectForKey:@"CFBundleIdentifier"] UTF8String];
+ if (!name) {
+ name = [[[bundle infoDictionary] objectForKey:@"CFBundleDisplayName"] UTF8String];
+ if (!name) {
+ name = [[[bundle infoDictionary] objectForKey:@"CFBundleName"] UTF8String];
+ if (!name) {
+ name = [[[bundle infoDictionary] objectForKey:@"CFBundleExecutable"] UTF8String];
+ if (!name) {
+ name = [[[NSProcessInfo processInfo] processName] UTF8String]; // oh well.
+ }
+ }
+ }
+ }
+
+ return name ? SDL_strdup(name) : NULL;
+ }
}
char *SDL_SYS_GetPrefPath(const char *org, const char *app)
diff --git a/src/filesystem/haiku/SDL_sysfilesystem.cc b/src/filesystem/haiku/SDL_sysfilesystem.cc
index 747bd6ced0130..7a051135ed9a3 100644
--- a/src/filesystem/haiku/SDL_sysfilesystem.cc
+++ b/src/filesystem/haiku/SDL_sysfilesystem.cc
@@ -44,6 +44,7 @@ char *SDL_SYS_GetBasePath(void)
return NULL;
}
+ // !!! FIXME: if find_path promises an absolute path, can we dump this and just do SDL_strrchr(name, '/')?
BEntry entry(name, true);
BPath path;
status_t rc = entry.GetPath(&path); // (path) now has binary's path.
@@ -66,8 +67,12 @@ char *SDL_SYS_GetBasePath(void)
char *SDL_SYS_GetExeName(void)
{
- SDL_Unsupported(); // !!! FIXME: Move most of SDL_SYS_GetBasePath to a separate function and reuse it here.
- return NULL;
+ char name[MAXPATHLEN];
+ if (find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, name, sizeof(name)) != B_OK) {
+ return NULL;
+ }
+ char *ptr = SDL_strrchr(name, '/');
+ return SDL_strdup(ptr ? ptr + 1 : name);
}
char *SDL_SYS_GetPrefPath(const char *org, const char *app)
diff --git a/src/filesystem/ngage/SDL_sysfilesystem.c b/src/filesystem/ngage/SDL_sysfilesystem.c
index 73b8ddc0c74bd..8ce3ee1512faa 100644
--- a/src/filesystem/ngage/SDL_sysfilesystem.c
+++ b/src/filesystem/ngage/SDL_sysfilesystem.c
@@ -21,6 +21,7 @@
#include "SDL_internal.h"
extern void NGAGE_GetAppPath(char *path);
+extern void NGAGE_GetExeName(char *path);
char *SDL_SYS_GetBasePath(void)
{
@@ -32,8 +33,9 @@ char *SDL_SYS_GetBasePath(void)
char *SDL_SYS_GetExeName(void)
{
- SDL_Unsupported(); // !!! FIXME: see code in NGAGE_GetAppPath
- return NULL;
+ char exe_name[512];
+ NGAGE_GetExeName(exe_name);
+ return SDL_strdup(exe_name);
}
char *SDL_SYS_GetPrefPath(const char *org, const char *app)
diff --git a/src/filesystem/ngage/SDL_sysfilesystem.cpp b/src/filesystem/ngage/SDL_sysfilesystem.cpp
index 622c82e465a96..415fe55c4dcc8 100644
--- a/src/filesystem/ngage/SDL_sysfilesystem.cpp
+++ b/src/filesystem/ngage/SDL_sysfilesystem.cpp
@@ -63,6 +63,23 @@ void NGAGE_GetAppPath(char *path)
}
}
+void NGAGE_GetExeName(char *path)
+{
+ TBuf<512> aPath;
+
+ TFileName fullExePath = RProcess().FileName();
+
+ TParsePtrC parser(fullExePath);
+ aPath.Copy(parser.NameAndExt());
+
+ TBuf8<512> utf8Path; // Temporary buffer for UTF-8 data.
+ CnvUtfConverter::ConvertFromUnicodeToUtf8(utf8Path, aPath);
+
+ // Copy UTF-8 data to the provided char* buffer.
+ strncpy(path, (const char *)utf8Path.Ptr(), utf8Path.Length());
+ path[utf8Path.Length()] = '\0';
+}
+
#ifdef __cplusplus
}
#endif
diff --git a/src/filesystem/riscos/SDL_sysfilesystem.c b/src/filesystem/riscos/SDL_sysfilesystem.c
index 2a5ac9263fa07..b616864122ada 100644
--- a/src/filesystem/riscos/SDL_sysfilesystem.c
+++ b/src/filesystem/riscos/SDL_sysfilesystem.c
@@ -154,8 +154,25 @@ char *SDL_SYS_GetBasePath(void)
char *SDL_SYS_GetExeName(void)
{
- SDL_Unsupported(); // !!! FIXME: see code in SDL_SYS_GetBasePath.
- return NULL;
+ _kernel_swi_regs regs;
+ _kernel_oserror *error;
+ char *canon, *ptr, *retval;
+
+ error = _kernel_swi(OS_GetEnv, ®s, ®s);
+ if (error) {
+ return NULL;
+ }
+
+ canon = canonicalisePath((const char *)regs.r[0], "Run$Path");
+ if (!canon) {
+ return NULL;
+ }
+
+ // find filename.
+ ptr = SDL_strrchr(canon, '.');
+ retval = SDL_strdup(ptr ? ptr + 1 : canon);
+ SDL_free(canon);
+ return retval;
}
char *SDL_SYS_GetPrefPath(const char *org, const char *app)
diff --git a/test/testfilesystem.c b/test/testfilesystem.c
index 87ba39501f47c..3e522c3e609e6 100644
--- a/test/testfilesystem.c
+++ b/test/testfilesystem.c
@@ -94,6 +94,9 @@ int main(int argc, char *argv[])
char *curdir;
const char *base_path;
+ /* this will be SDL's best guess at the human-readable exe name (or bundle id, or whatever) by default. */
+ SDL_Log("Default app name: '%s'", SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING));
+
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, 0);
if (!state) {