From 3f2226a917a2a3aefb8daabd42d939099f16f28d Mon Sep 17 00:00:00 2001 From: GamesTrap Date: Wed, 7 May 2025 00:16:16 +0200 Subject: [PATCH] Add progress bar support for Linux --- CMakeLists.txt | 1 + docs/README-wayland.md | 9 ++ src/core/linux/SDL_dbus.c | 1 + src/core/linux/SDL_dbus.h | 1 + src/core/linux/SDL_progressbar.c | 159 +++++++++++++++++++++++++++ src/core/linux/SDL_progressbar.h | 30 +++++ src/video/SDL_video.c | 6 + src/video/wayland/SDL_waylandvideo.c | 4 + src/video/x11/SDL_x11video.c | 4 + 9 files changed, 215 insertions(+) create mode 100644 src/core/linux/SDL_progressbar.c create mode 100644 src/core/linux/SDL_progressbar.h diff --git a/CMakeLists.txt b/CMakeLists.txt index db0eb1cadb..a36d82acdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1736,6 +1736,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_dbus.c" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_theme.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_progressbar.c" ) endif() diff --git a/docs/README-wayland.md b/docs/README-wayland.md index 75a9b906e1..a3cd06f78a 100644 --- a/docs/README-wayland.md +++ b/docs/README-wayland.md @@ -59,6 +59,15 @@ encounter limitations or behavior that is different from other windowing systems `SDL_APP_ID` hint string, the desktop entry file name should match the application ID. For example, if your application ID is set to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`. +### The application progress bar can't be set via ```SDL_SetWindowProgressState()``` or ```SDL_SetWindowProgressValue()``` + +- Only some Desktop Environemnts support the underlying API. Known compatible DEs: Unity, KDE +- The underlying API requires a desktop entry file, aka a `.desktop` file. + Please see the [Desktop Entry Specification](https://specifications.freedesktop.org/desktop-entry-spec/latest/) for + more information on the format of this file. Note that if your application manually sets the application ID via the + `SDL_APP_ID` hint string, the desktop entry file name should match the application ID. For example, if your + application ID is set to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`. + ### Keyboard grabs don't work when running under XWayland - On GNOME based desktops, the dconf setting `org/gnome/mutter/wayland/xwayland-allow-grabs` must be enabled. diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 226a7f3293..b61a1cd920 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -68,6 +68,7 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path); SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call); + SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *), message_new_signal); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist); SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append); diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 097bc31eb3..230b20fded 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -67,6 +67,7 @@ typedef struct SDL_DBusContext dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *); dbus_bool_t (*message_has_path)(DBusMessage *, const char *); DBusMessage *(*message_new_method_call)(const char *, const char *, const char *, const char *); + DBusMessage *(*message_new_signal)(const char *, const char *, const char *); dbus_bool_t (*message_append_args)(DBusMessage *, int, ...); dbus_bool_t (*message_append_args_valist)(DBusMessage *, int, va_list); void (*message_iter_init_append)(DBusMessage *, DBusMessageIter *); diff --git a/src/core/linux/SDL_progressbar.c b/src/core/linux/SDL_progressbar.c new file mode 100644 index 0000000000..e50f8361ca --- /dev/null +++ b/src/core/linux/SDL_progressbar.c @@ -0,0 +1,159 @@ +/* + 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_progressbar.h" +#include "SDL_internal.h" + +#include "SDL_dbus.h" + +#ifdef SDL_USE_LIBDBUS + +#include + +#include "../unix/SDL_appid.h" + +#define UnityLauncherAPI_DBUS_INTERFACE "com.canonical.Unity.LauncherEntry" +#define UnityLauncherAPI_DBUS_SIGNAL "Update" + +static char *GetDBUSObjectPath() +{ + char *app_id = SDL_strdup(SDL_GetAppID()); + + if (!app_id) { + return NULL; + } + + // Sanitize exe_name to make it a legal D-Bus path element + for (char *p = app_id; *p; ++p) { + if (!SDL_isalnum(*p)) { + *p = '_'; + } + } + + // Ensure it starts with a letter or underscore + if (!SDL_isalpha(app_id[0]) && app_id[0] != '_') { + SDL_memmove(app_id + 1, app_id, SDL_strlen(app_id) + 1); + app_id[0] = '_'; + } + + // Create full path + char path[1024]; + SDL_snprintf(path, sizeof(path), "/org/libsdl/%s_%d", app_id, getpid()); + + SDL_free(app_id); + + return SDL_strdup(path); +} + +static char *GetAppDesktopPath() +{ + const char *desktop_suffix = ".desktop"; + const char *app_id = SDL_GetAppID(); + const size_t desktop_path_total_length = SDL_strlen(app_id) + SDL_strlen(desktop_suffix) + 1; + char *desktop_path = (char *)SDL_malloc(desktop_path_total_length); + if (!desktop_path) { + return NULL; + } + *desktop_path = '\0'; + SDL_strlcat(desktop_path, app_id, desktop_path_total_length); + SDL_strlcat(desktop_path, desktop_suffix, desktop_path_total_length); + + return desktop_path; +} + +static int ShouldShowProgress(SDL_ProgressState progressState) +{ + if (progressState == SDL_PROGRESS_STATE_INVALID || + progressState == SDL_PROGRESS_STATE_NONE) { + return 0; + } + + // Unity LauncherAPI only supports "normal" display of progress + return 1; +} + +bool DBUS_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window) +{ + // Signal signature: + // signal com.canonical.Unity.LauncherEntry.Update (in s app_uri, in a{sv} properties) + + SDL_DBusContext *dbus = SDL_DBus_GetContext(); + + if (!dbus || !dbus->session_conn) { + return false; + } + + char *objectPath = GetDBUSObjectPath(); + if (!objectPath) { + return false; + } + + DBusMessage *msg = dbus->message_new_signal(objectPath, UnityLauncherAPI_DBUS_INTERFACE, UnityLauncherAPI_DBUS_SIGNAL); + if (!msg) { + SDL_free(objectPath); + return false; + } + + char *desktop_path = GetAppDesktopPath(); + if (!desktop_path) { + dbus->message_unref(msg); + SDL_free(objectPath); + return false; + } + + const char *progress_visible_str = "progress-visible"; + const char *progress_str = "progress"; + int dbus_type_boolean_str = DBUS_TYPE_BOOLEAN; + int dbus_type_double_str = DBUS_TYPE_DOUBLE; + + const int progress_visible = ShouldShowProgress(window->progress_state); + double progress = (double)window->progress_value; + + DBusMessageIter args, props; + dbus->message_iter_init_append(msg, &args); + dbus->message_iter_append_basic(&args, DBUS_TYPE_STRING, &desktop_path); // Setup app_uri paramter + dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &props); // Setup properties parameter + DBusMessageIter key_it, value_it; + // Set progress visible property + dbus->message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY, NULL, &key_it); + dbus->message_iter_append_basic(&key_it, DBUS_TYPE_STRING, &progress_visible_str); // Append progress-visible key data + dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, (const char *)&dbus_type_boolean_str, &value_it); + dbus->message_iter_append_basic(&value_it, DBUS_TYPE_BOOLEAN, &progress_visible); // Append progress-visible value data + dbus->message_iter_close_container(&key_it, &value_it); + dbus->message_iter_close_container(&props, &key_it); + // Set progress value property + dbus->message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY, NULL, &key_it); + dbus->message_iter_append_basic(&key_it, DBUS_TYPE_STRING, &progress_str); // Append progress key data + dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, (const char *)&dbus_type_double_str, &value_it); + dbus->message_iter_append_basic(&value_it, DBUS_TYPE_DOUBLE, &progress); // Append progress value data + dbus->message_iter_close_container(&key_it, &value_it); + dbus->message_iter_close_container(&props, &key_it); + dbus->message_iter_close_container(&args, &props); + + dbus->connection_send(dbus->session_conn, msg, NULL); + + SDL_free(desktop_path); + dbus->message_unref(msg); + SDL_free(objectPath); + + return true; +} + +#endif // SDL_USE_LIBDBUS diff --git a/src/core/linux/SDL_progressbar.h b/src/core/linux/SDL_progressbar.h new file mode 100644 index 0000000000..da9b815f4f --- /dev/null +++ b/src/core/linux/SDL_progressbar.h @@ -0,0 +1,30 @@ +/* + 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. +*/ + +#ifndef SDL_prograssbar_h_ +#define SDL_prograssbar_h_ + +#include "../../video/SDL_sysvideo.h" +#include "SDL_internal.h" + +extern bool DBUS_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window); + +#endif // SDL_prograssbar_h_ diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index c9eb1caa75..294fad5d10 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -2266,6 +2266,12 @@ static void SDL_FinishWindowCreation(SDL_Window *window, SDL_WindowFlags flags) SDL_ShowWindow(window); } } + +#if defined(SDL_PLATFORM_LINUX) + // On Linux the progress state is persisted throughout multiple program runs, so reset state on window creation + SDL_SetWindowProgressState(window, SDL_PROGRESS_STATE_NONE); + SDL_SetWindowProgressValue(window, 0.0f); +#endif } static bool SDL_ContextNotSupported(const char *name) diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 516419c281..b615bf7079 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -24,6 +24,7 @@ #ifdef SDL_VIDEO_DRIVER_WAYLAND #include "../../core/linux/SDL_system_theme.h" +#include "../../core/linux/SDL_progressbar.h" #include "../../events/SDL_events_c.h" #include "SDL_waylandclipboard.h" @@ -629,6 +630,9 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) device->DestroyWindow = Wayland_DestroyWindow; device->SetWindowHitTest = Wayland_SetWindowHitTest; device->FlashWindow = Wayland_FlashWindow; +#ifdef SDL_USE_LIBDBUS + device->ApplyWindowProgress = DBUS_ApplyWindowProgress; +#endif // SDL_USE_LIBDBUS device->HasScreenKeyboardSupport = Wayland_HasScreenKeyboardSupport; device->ShowWindowSystemMenu = Wayland_ShowWindowSystemMenu; device->SyncWindow = Wayland_SyncWindow; diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index f371f519d6..39fc1e278f 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -25,6 +25,7 @@ #include // For getpid() and readlink() #include "../../core/linux/SDL_system_theme.h" +#include "../../core/linux/SDL_progressbar.h" #include "../../events/SDL_keyboard_c.h" #include "../../events/SDL_mouse_c.h" #include "../SDL_pixels_c.h" @@ -204,6 +205,9 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->AcceptDragAndDrop = X11_AcceptDragAndDrop; device->UpdateWindowShape = X11_UpdateWindowShape; device->FlashWindow = X11_FlashWindow; +#ifdef SDL_USE_LIBDBUS + device->ApplyWindowProgress = DBUS_ApplyWindowProgress; +#endif // SDL_USE_LIBDBUS device->ShowWindowSystemMenu = X11_ShowWindowSystemMenu; device->SetWindowFocusable = X11_SetWindowFocusable; device->SyncWindow = X11_SyncWindow;