From 3f2226a917a2a3aefb8daabd42d939099f16f28d Mon Sep 17 00:00:00 2001
From: GamesTrap <[EMAIL REDACTED]>
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 db0eb1cadbd5f..a36d82acdd50c 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 75a9b906e106f..a3cd06f78a5fd 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 226a7f3293d56..b61a1cd920e6f 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 097bc31eb35ab..230b20fded057 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 0000000000000..e50f8361ca161
--- /dev/null
+++ b/src/core/linux/SDL_progressbar.c
@@ -0,0 +1,159 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2025 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_progressbar.h"
+#include "SDL_internal.h"
+
+#include "SDL_dbus.h"
+
+#ifdef SDL_USE_LIBDBUS
+
+#include <unistd.h>
+
+#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 0000000000000..da9b815f4f12d
--- /dev/null
+++ b/src/core/linux/SDL_progressbar.h
@@ -0,0 +1,30 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2025 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.
+*/
+
+#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 c9eb1caa75c40..294fad5d10e51 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 516419c2818c4..b615bf707905c 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 f371f519d6be0..39fc1e278f9d0 100644
--- a/src/video/x11/SDL_x11video.c
+++ b/src/video/x11/SDL_x11video.c
@@ -25,6 +25,7 @@
#include <unistd.h> // 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;