diff --git a/CMakeLists.txt b/CMakeLists.txt index 0db59b3eec21c..ce4fa98834c4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1791,6 +1791,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_evdev_capabilities.c" "${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 0000000000000..da41934def1e4 --- /dev/null +++ b/docs/README-ubuntu-touch.md @@ -0,0 +1,38 @@ +# 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//`, where +`` is the identifier string for the app. It can be found in the +`manifest.json` file under the key `"name"`, and usually looks like +`appname.yourname`. + +A future version of SDL may change how `SDL_GetPrefPath` operates on Ubuntu +Touch to make it ignore its arguments and always choose the correct writable +folder. For future-proof-ness, using `SDL_GetPrefPath("", "")` as a +writable directory is a safe choice. + +### 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 294089ff4a337..75b8d0d33f9f7 100644 --- a/include/SDL3/SDL_system.h +++ b/include/SDL3/SDL_system.h @@ -631,7 +631,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; /** @@ -809,6 +810,22 @@ 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.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_IsUbuntuTouch(void); + +#endif + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/src/SDL.c b/src/SDL.c index 502f6617a4f26..d2d52c6726471 100644 --- a/src/SDL.c +++ b/src/SDL.c @@ -804,6 +804,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/core/SDL_core_unsupported.c b/src/core/SDL_core_unsupported.c index af963ed8f43a4..ae7e4dfc5d42b 100644 --- a/src/core/SDL_core_unsupported.c +++ b/src/core/SDL_core_unsupported.c @@ -211,3 +211,13 @@ Sint32 JNI_OnLoad(void *vm, void *reserved) return -1; // JNI_ERR } #endif + +#ifndef SDL_PLATFORM_LINUX + +SDL_DECLSPEC bool SDLCALL SDL_IsUbuntuTouch(void); +bool SDL_IsUbuntuTouch(void) +{ + return false; +} + +#endif diff --git a/src/core/linux/SDL_system_theme.c b/src/core/linux/SDL_system_theme.c index 6d6087db5a37f..e8588c93ea546 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 @@ bool SDL_SystemTheme_Init(void) 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 0000000000000..ea5b3bc176f9c --- /dev/null +++ b/src/core/linux/SDL_ubuntu_touch.c @@ -0,0 +1,32 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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) +{ + return access("/etc/ubuntu-touch-session.d/", F_OK) == 0; +} + +#endif diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 44f9d0e062a36..11e18e9d9fad3 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1250,6 +1250,7 @@ SDL3_0.0.0 { SDL_GetRenderTextureAddressMode; SDL_GetGPUDeviceProperties; SDL_CreateGPURenderer; + 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 a12f3fbf24b4a..d153817e5c75d 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1275,3 +1275,4 @@ #define SDL_GetRenderTextureAddressMode SDL_GetRenderTextureAddressMode_REAL #define SDL_GetGPUDeviceProperties SDL_GetGPUDeviceProperties_REAL #define SDL_CreateGPURenderer SDL_CreateGPURenderer_REAL +#define SDL_IsUbuntuTouch SDL_IsUbuntuTouch_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index d7988ac2b0944..7a535f1759304 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1283,3 +1283,4 @@ SDL_DYNAPI_PROC(bool,SDL_SetRenderTextureAddressMode,(SDL_Renderer *a,SDL_Textur SDL_DYNAPI_PROC(bool,SDL_GetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode *b,SDL_TextureAddressMode *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGPUDeviceProperties,(SDL_GPUDevice *a),(a),return) SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_Window *a,SDL_GPUShaderFormat b,SDL_GPUDevice **c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_IsUbuntuTouch,(void),(),return) diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index db2637bce13c6..a78b281b747c7 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -644,6 +644,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); }