From e52dbf44423f4919025ed8cba828a3f84829f6d0 Mon Sep 17 00:00:00 2001 From: Semphriss <66701383+Semphriss@users.noreply.github.com> Date: Sun, 21 Jun 2026 14:51:15 -0400 Subject: [PATCH] Add basic Ubuntu Touch functions #12543 This adds support for: * System theme * Sandbox detection * Device form factor detection Many things aren't properly supported yet, but changes and upgrades will happen on the Ubuntu Touch side, so SDL should automatically support more Ubuntu Touch features as time goes. --- CMakeLists.txt | 1 + docs/README-ubuntu-touch.md | 32 +++++++++ include/SDL3/SDL_system.h | 56 ++++++++++++++- src/SDL.c | 16 +++++ src/SDL_properties.c | 9 +++ src/core/SDL_core_unsupported.c | 9 +++ src/core/SDL_core_unsupported.h | 4 ++ src/core/linux/SDL_system_theme.c | 49 ++++++++++++++ src/core/linux/SDL_ubuntu_touch.c | 90 +++++++++++++++++++++++++ src/core/linux/SDL_ubuntu_touch.h | 27 ++++++++ src/dynapi/SDL_dynapi.exports | 1 + src/dynapi/SDL_dynapi.sym | 1 + src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + src/filesystem/unix/SDL_sysfilesystem.c | 21 ++++++ src/video/SDL_video.c | 6 ++ test/testsymbols.c | 1 + 17 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 docs/README-ubuntu-touch.md create mode 100644 src/core/linux/SDL_ubuntu_touch.c create mode 100644 src/core/linux/SDL_ubuntu_touch.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a86cfb187e..cd08097c50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2142,6 +2142,7 @@ elseif(UNIX AND NOT (APPLE OR RISCOS OR HAIKU OR CYGWIN)) "${SDL3_SOURCE_DIR}/src/core/linux/SDL_evdev_capabilities.c" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_evdev_capabilities.h" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_threadprio.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_ubuntu_touch.c" ) # src/core/unix/*.c is included in a generic if(UNIX) section, elsewhere. diff --git a/docs/README-ubuntu-touch.md b/docs/README-ubuntu-touch.md new file mode 100644 index 0000000000..fe8926922a --- /dev/null +++ b/docs/README-ubuntu-touch.md @@ -0,0 +1,32 @@ +# Ubuntu Touch / Lomiri + +Ubuntu Touch being similar to Ubuntu desktop, most features should be supported +out-of-the-box with SDL. + +## Developing apps + +Ubuntu Touch apps are developed using [Clickable](https://clickable-ut.dev/). + +Clickable provides an SDL template. It is highly recommended to use the template +as a starting point for both new and existing apps. + +## Considerations + +Ubuntu Touch is similar to the desktop version of Ubuntu, but presents some +differences in behavior. Developers should be wary of the following: + +### SDL_GetPrefPath + +The only allowed writable folder is `~/.local/share//`. +`SDL_GetPrefPath` ignores its arguments and will always return that path on +Ubuntu Touch. No changes are needed in apps. + +### Video driver + +Currently, [a bug](https://github.com/libsdl-org/SDL/issues/12247) forces SDL to +use the Wayland driver on Ubuntu Touch. No changes are needed in apps. + +### Extra functions + +SDL provides `SDL_IsUbuntuTouch()` to differentiate between Ubuntu Touch and +regular Unix, which can help if certain platform-specific tweaks are needed. diff --git a/include/SDL3/SDL_system.h b/include/SDL3/SDL_system.h index 2d3d6e9d80..0f9e72cb3a 100644 --- a/include/SDL3/SDL_system.h +++ b/include/SDL3/SDL_system.h @@ -725,7 +725,8 @@ typedef enum SDL_Sandbox SDL_SANDBOX_UNKNOWN_CONTAINER, SDL_SANDBOX_FLATPAK, SDL_SANDBOX_SNAP, - SDL_SANDBOX_MACOS + SDL_SANDBOX_MACOS, + SDL_SANDBOX_LOMIRI } SDL_Sandbox; /** @@ -903,6 +904,59 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetGDKDefaultUser(XUserHandle *outUserHandl #endif +/* + * Functions used only with Ubuntu Touch + */ +#ifdef SDL_PLATFORM_LINUX + +/** + * Detect whether the current platform is Ubuntu Touch. + * + * \returns true if the platform is Ubuntu Touch; false otherwise. + * + * \since This function is available since SDL 3.6.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_IsUbuntuTouch(void); + +/** + * The ID of the application on Ubuntu Touch, as reported in the manifest. + * + * This is often called the "App Name"; the human-readable name for an app is + * called the "App Title". + * + * This string is needed by some low-level OS features to operate properly. + * + * \since This macro is available since SDL 3.6.0. + * + * \sa SDL_IsUbuntuTouch + */ +#define SDL_PROP_GLOBAL_SYSTEM_UBUNTU_TOUCH_APPID_STRING "SDL.system.ubuntu_touch.appid" + +/** + * The identifier for the specific hook which launched the current executable, + * as reported in the manifest. + * + * This is relevant for application packages that ship multiple applications + * with their desktop files; they will have the same app ID but will differ by + * their hook. + * + * \since This macro is available since SDL 3.6.0. + * + * \sa SDL_IsUbuntuTouch + */ +#define SDL_PROP_GLOBAL_SYSTEM_UBUNTU_TOUCH_HOOK_STRING "SDL.system.ubuntu_touch.hook" + +/** + * The version of the application on Ubuntu Touch, as reported in the manifest. + * + * \since This macro is available since SDL 3.6.0. + * + * \sa SDL_IsUbuntuTouch + */ +#define SDL_PROP_GLOBAL_SYSTEM_UBUNTU_TOUCH_APP_VERSION_STRING "SDL.system.ubuntu_touch.app_version" + +#endif + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/src/SDL.c b/src/SDL.c index a903b16743..b96ee54aa6 100644 --- a/src/SDL.c +++ b/src/SDL.c @@ -169,6 +169,16 @@ const char *SDL_GetAppMetadataProperty(const char *name) value = SDL_GetStringProperty(SDL_GetGlobalProperties(), name, NULL); } if (!value || !*value) { +#ifdef SDL_PLATFORM_LINUX + if (SDL_IsUbuntuTouch()) { + if (SDL_strcmp(name, SDL_PROP_APP_METADATA_IDENTIFIER_STRING) == 0) { + value = SDL_GetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_SYSTEM_UBUNTU_TOUCH_APPID_STRING, NULL); + } else if (SDL_strcmp(name, SDL_PROP_APP_METADATA_VERSION_STRING) == 0) { + value = SDL_GetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_SYSTEM_UBUNTU_TOUCH_APP_VERSION_STRING, NULL); + } + } +#endif + if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0) { value = SDL_GetExeName(); if (!value) { @@ -915,6 +925,12 @@ static SDL_Sandbox SDL_DetectSandbox(void) return SDL_SANDBOX_SNAP; } + /* Ubuntu Touch also supports Snap; check for classic sandboxing only if + * Snap hasn't been detected. */ + if (SDL_getenv("LOMIRI_APPLICATION_ISOLATION") || SDL_getenv("CLICKABLE_DESKTOP_MODE")) { + return SDL_SANDBOX_LOMIRI; + } + if (access("/run/host/container-manager", F_OK) == 0) { return SDL_SANDBOX_UNKNOWN_CONTAINER; } diff --git a/src/SDL_properties.c b/src/SDL_properties.c index 762af38b9b..f91db7de5a 100644 --- a/src/SDL_properties.c +++ b/src/SDL_properties.c @@ -22,6 +22,7 @@ #include "SDL_hints_c.h" #include "SDL_properties_c.h" +#include "core/linux/SDL_ubuntu_touch.h" typedef struct @@ -146,6 +147,14 @@ SDL_PropertiesID SDL_GetGlobalProperties(void) props = SDL_CreateProperties(); // Set global platform properties +#ifdef SDL_PLATFORM_LINUX + if (SDL_IsUbuntuTouch()) { + if (!SDL_SetupUbuntuTouchGlobalProperties(props)) { + SDL_DestroyProperties(props); + return 0; + } + } +#endif if (!SDL_CompareAndSwapAtomicU32(&SDL_global_properties, 0, props)) { // Somebody else created global properties before us, just use those diff --git a/src/core/SDL_core_unsupported.c b/src/core/SDL_core_unsupported.c index 09b675d6ec..7f9dbbe0dc 100644 --- a/src/core/SDL_core_unsupported.c +++ b/src/core/SDL_core_unsupported.c @@ -195,3 +195,12 @@ Sint32 JNI_OnLoad(JavaVM *vm, void *reserved) return 0x00010004; // JNI_VERSION_1_4 } #endif + +#ifndef SDL_PLATFORM_LINUX + +bool SDL_IsUbuntuTouch(void) +{ + return false; +} + +#endif diff --git a/src/core/SDL_core_unsupported.h b/src/core/SDL_core_unsupported.h index b61a9a3a2f..d100836019 100644 --- a/src/core/SDL_core_unsupported.h +++ b/src/core/SDL_core_unsupported.h @@ -65,3 +65,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void); extern SDL_DECLSPEC bool SDLCALL SDL_IsDeXMode(void); extern SDL_DECLSPEC Sint32 SDLCALL JNI_OnLoad(JavaVM *vm, void *reserved); #endif /* !SDL_PLATFORM_ANDROID */ + +#if !defined(SDL_PLATFORM_LINUX) +extern SDL_DECLSPEC bool SDLCALL SDL_IsUbuntuTouch(void); +#endif /* !SDL_PLATFORM_LINUX */ diff --git a/src/core/linux/SDL_system_theme.c b/src/core/linux/SDL_system_theme.c index 100661d6d5..a2a9a7100c 100644 --- a/src/core/linux/SDL_system_theme.c +++ b/src/core/linux/SDL_system_theme.c @@ -24,6 +24,7 @@ #include "SDL_system_theme.h" #include "../../video/SDL_sysvideo.h" +#include #include #define PORTAL_DESTINATION "org.freedesktop.portal.Desktop" @@ -150,7 +151,55 @@ incorrect_type: return true; } +SDL_SystemTheme UbuntuTouch_GetSystemTheme(void) +{ + SDL_SystemTheme theme = SDL_SYSTEM_THEME_UNKNOWN; + FILE *config_file = NULL; + char *line = NULL; + size_t line_alloc = 0; + ssize_t line_size = 0; + bool is_in_general_category = false; + + // "Lomiri": Ubuntu Touch 20.04+ + // "Ubuntu": Ubuntu Touch 16.04 + config_file = fopen("/home/phablet/.config/lomiri-ui-toolkit/theme.ini", "r"); + if (!config_file) { + config_file = fopen("/home/phablet/.config/ubuntu-ui-toolkit/theme.ini", "r"); + if (!config_file) { + return SDL_SYSTEM_THEME_UNKNOWN; + } + } + + while ((line_size = getline(&line, &line_alloc, config_file)) != -1) { + if (line_size >= 1 && line[0] == '[') { + is_in_general_category = SDL_strcmp(line, "[General]\n") == 0; + } else if (is_in_general_category && SDL_strncmp(line, "theme=", 6) == 0) { + if (SDL_strcmp(line, "theme=Lomiri.Components.Themes.SuruDark\n") == 0 || + SDL_strcmp(line, "theme=Ubuntu.Components.Themes.SuruDark\n") == 0) { + theme = SDL_SYSTEM_THEME_DARK; + } else if (SDL_strcmp(line, "theme=Lomiri.Components.Themes.Ambiance\n") == 0 || + SDL_strcmp(line, "theme=Ubuntu.Components.Themes.Ambiance\n") == 0) { + theme = SDL_SYSTEM_THEME_LIGHT; + } else { + theme = SDL_SYSTEM_THEME_UNKNOWN; + } + } + } + + free(line); // This should NOT be SDL_free() + + fclose(config_file); + + return theme; +} + SDL_SystemTheme SDL_SystemTheme_Get(void) { + if (system_theme_data.theme == SDL_SYSTEM_THEME_UNKNOWN) { + // TODO: Use inotify to watch for changes, so that the config file + // doesn't need to be checked each time. + return UbuntuTouch_GetSystemTheme(); + } + return system_theme_data.theme; } diff --git a/src/core/linux/SDL_ubuntu_touch.c b/src/core/linux/SDL_ubuntu_touch.c new file mode 100644 index 0000000000..102b45bcfd --- /dev/null +++ b/src/core/linux/SDL_ubuntu_touch.c @@ -0,0 +1,90 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_PLATFORM_LINUX +#include +#include + +bool SDL_IsUbuntuTouch(void) +{ + static bool checked = false; + static bool is_ubuntu_touch = false; + + if (!checked) { + const char *xdg_session_desktop = SDL_getenv("XDG_SESSION_DESKTOP"); + if (xdg_session_desktop && SDL_strcmp(xdg_session_desktop, "ubuntu-touch") == 0) { + is_ubuntu_touch = true; + } + checked = true; + } + return is_ubuntu_touch; +} + +bool SDL_SetupUbuntuTouchGlobalProperties(SDL_PropertiesID props) +{ + if (!SDL_IsUbuntuTouch()) { + return true; + } + + // Format is __, like myapp.myname_myapp_1.0.0. + // None of those are allowed to have underscores. + const char *app_id = SDL_getenv("APP_ID"); + if (!app_id) { + SDL_SetError("Missing APP_ID"); + return false; + } + + char *buffer = SDL_strdup(app_id); + if (!buffer) { + return false; + } + + char *it = SDL_strchr(buffer, '_'); + if (!*it) { + SDL_SetError("Malformed APP_ID"); + SDL_free(buffer); + return NULL; + } + + char *it2 = SDL_strchr(it + 1, '_'); + if (!*it2) { + SDL_SetError("Malformed APP_ID"); + SDL_free(buffer); + return NULL; + } + + *it++ = '\0'; + *it2++ = '\0'; + + if (!SDL_SetStringProperty(props, SDL_PROP_GLOBAL_SYSTEM_UBUNTU_TOUCH_APPID_STRING, buffer) || + !SDL_SetStringProperty(props, SDL_PROP_GLOBAL_SYSTEM_UBUNTU_TOUCH_HOOK_STRING, it) || + !SDL_SetStringProperty(props, SDL_PROP_GLOBAL_SYSTEM_UBUNTU_TOUCH_APP_VERSION_STRING, it2)) { + SDL_free(buffer); + return false; + } + + SDL_free(buffer); + + return true; +} + +#endif diff --git a/src/core/linux/SDL_ubuntu_touch.h b/src/core/linux/SDL_ubuntu_touch.h new file mode 100644 index 0000000000..70a9feb351 --- /dev/null +++ b/src/core/linux/SDL_ubuntu_touch.h @@ -0,0 +1,27 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_PLATFORM_LINUX + +bool SDL_SetupUbuntuTouchGlobalProperties(SDL_PropertiesID props); + +#endif diff --git a/src/dynapi/SDL_dynapi.exports b/src/dynapi/SDL_dynapi.exports index f461c2c074..87e38965c9 100644 --- a/src/dynapi/SDL_dynapi.exports +++ b/src/dynapi/SDL_dynapi.exports @@ -1300,3 +1300,4 @@ _SDL_ShowNotification _SDL_RemoveNotification _SDL_GetDeviceFormFactor _SDL_GetDeviceFormFactorName +_SDL_IsUbuntuTouch diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 778de46710..607222b2b7 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1301,6 +1301,7 @@ SDL3_0.0.0 { SDL_RemoveNotification; SDL_GetDeviceFormFactor; SDL_GetDeviceFormFactorName; + SDL_IsUbuntuTouch; # 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 1ce6de5fae..b2d767829f 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1327,3 +1327,4 @@ #define SDL_RemoveNotification SDL_RemoveNotification_REAL #define SDL_GetDeviceFormFactor SDL_GetDeviceFormFactor_REAL #define SDL_GetDeviceFormFactorName SDL_GetDeviceFormFactorName_REAL +#define SDL_IsUbuntuTouch SDL_IsUbuntuTouch_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 807dbc2635..884cf2ae96 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1335,3 +1335,4 @@ SDL_DYNAPI_PROC(SDL_NotificationID,SDL_ShowNotification,(const char *a,const cha SDL_DYNAPI_PROC(bool,SDL_RemoveNotification,(SDL_NotificationID a),(a),return) SDL_DYNAPI_PROC(SDL_FormFactor,SDL_GetDeviceFormFactor,(void),(),return) SDL_DYNAPI_PROC(const char*,SDL_GetDeviceFormFactorName,(SDL_FormFactor a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_IsUbuntuTouch,(void),(),return) diff --git a/src/filesystem/unix/SDL_sysfilesystem.c b/src/filesystem/unix/SDL_sysfilesystem.c index 8c9259b5f1..127c285200 100644 --- a/src/filesystem/unix/SDL_sysfilesystem.c +++ b/src/filesystem/unix/SDL_sysfilesystem.c @@ -285,6 +285,27 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) char *result = NULL; char *ptr = NULL; +#ifdef SDL_PLATFORM_LINUX + if (SDL_IsUbuntuTouch()) { + // On Ubuntu Touch, the only allowed data folder is: + // ~/.local/share// + + SDL_PropertiesID props = SDL_GetGlobalProperties(); + if (!props) { + return NULL; + } + + const char *appid = SDL_GetStringProperty(props, SDL_PROP_GLOBAL_SYSTEM_UBUNTU_TOUCH_APPID_STRING, NULL); + if (!appid) { + SDL_SetError("Ubuntu Touch App ID missing from global properties"); + return NULL; + } + + app = appid; + org = ""; + } +#endif + if (!envr) { // You end up with "$HOME/.local/share/Game Name 2" envr = SDL_getenv("HOME"); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 5029fe7072..aed81189e3 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -657,6 +657,12 @@ bool SDL_VideoInit(const char *driver_name) // Select the proper video driver video = NULL; +#ifdef SDL_PLATFORM_LINUX + // https://github.com/libsdl-org/SDL/issues/12247 + if (SDL_IsUbuntuTouch()) { + driver_name = "wayland"; + } +#endif if (!driver_name) { driver_name = SDL_GetHint(SDL_HINT_VIDEO_DRIVER); } diff --git a/test/testsymbols.c b/test/testsymbols.c index cf363e6251..3b5591e6ac 100644 --- a/test/testsymbols.c +++ b/test/testsymbols.c @@ -53,6 +53,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetiOSEventPump(void); #if !defined(SDL_PLATFORM_LINUX) extern SDL_DECLSPEC void SDLCALL SDL_SetLinuxThreadPriority(void); extern SDL_DECLSPEC void SDLCALL SDL_SetLinuxThreadPriorityAndPolicy(void); +extern SDL_DECLSPEC bool SDLCALL SDL_IsUbuntuTouch(void); #endif #if !(defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK))