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.
This commit is contained in:
Semphriss 2026-06-21 14:51:15 -04:00 committed by GitHub
parent 35ebc3db89
commit e52dbf4442
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 324 additions and 1 deletions

View file

@ -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.

View file

@ -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/<appname>/`.
`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.

View file

@ -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
}

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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 */

View file

@ -24,6 +24,7 @@
#include "SDL_system_theme.h"
#include "../../video/SDL_sysvideo.h"
#include <stdio.h>
#include <unistd.h>
#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;
}

View file

@ -0,0 +1,90 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
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 <stdio.h>
#include <unistd.h>
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 <appname>_<hookname>_<version>, 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

View file

@ -0,0 +1,27 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
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

View file

@ -1300,3 +1300,4 @@ _SDL_ShowNotification
_SDL_RemoveNotification
_SDL_GetDeviceFormFactor
_SDL_GetDeviceFormFactorName
_SDL_IsUbuntuTouch

View file

@ -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: *;
};

View file

@ -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

View file

@ -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)

View file

@ -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/<app id>/
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");

View file

@ -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);
}

View file

@ -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))