From 7a10fcdcccf89c7c29724b5e0191b1b66820c07b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Tassoux?= <[EMAIL REDACTED]>
Date: Sat, 22 Mar 2025 16:04:10 +0100
Subject: [PATCH] Add progress bar support for Windows (#12530)
---
CMakeLists.txt | 1 +
cmake/PreseedMSVCCache.cmake | 1 +
include/SDL3/SDL_test_common.h | 2 +
include/SDL3/SDL_video.h | 42 ++++++++
include/build_config/SDL_build_config.h.cmake | 1 +
.../build_config/SDL_build_config_windows.h | 3 +
.../build_config/SDL_build_config_wingdk.h | 1 +
include/build_config/SDL_build_config_xbox.h | 1 +
src/dynapi/SDL_dynapi.sym | 2 +
src/dynapi/SDL_dynapi_overrides.h | 2 +
src/dynapi/SDL_dynapi_procs.h | 2 +
src/test/SDL_test_common.c | 25 +++++
src/video/SDL_sysvideo.h | 2 +
src/video/SDL_video.c | 24 +++++
src/video/windows/SDL_windowsevents.c | 10 ++
src/video/windows/SDL_windowsvideo.c | 17 ++++
src/video/windows/SDL_windowsvideo.h | 8 ++
src/video/windows/SDL_windowswindow.c | 95 ++++++++++++++++++-
src/video/windows/SDL_windowswindow.h | 2 +
19 files changed, 239 insertions(+), 2 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ed3a2d2ef1e9f..03f7e46ff7c7f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1979,6 +1979,7 @@ elseif(WINDOWS)
check_include_file(audioclient.h HAVE_AUDIOCLIENT_H)
check_include_file(sensorsapi.h HAVE_SENSORSAPI_H)
check_include_file(shellscalingapi.h HAVE_SHELLSCALINGAPI_H)
+ check_include_file(shobjidl_core.h HAVE_SHOBJIDL_CORE_H)
check_c_source_compiles("
#include <windows.h>
#include <mfapi.h>
diff --git a/cmake/PreseedMSVCCache.cmake b/cmake/PreseedMSVCCache.cmake
index 17495aa480aba..23025eb0cb786 100644
--- a/cmake/PreseedMSVCCache.cmake
+++ b/cmake/PreseedMSVCCache.cmake
@@ -15,6 +15,7 @@ if(MSVC)
set(HAVE_MMDEVICEAPI_H "1" CACHE INTERNAL "Have include mmdeviceapi.h")
set(HAVE_SENSORSAPI_H "1" CACHE INTERNAL "Have include sensorsapi.h")
set(HAVE_SHELLSCALINGAPI_H "1" CACHE INTERNAL "Have include shellscalingapi.h")
+ set(HAVE_SHOBJIDL_CORE_H "1" CACHE INTERNAL "Have include shobjidl_core.h")
set(HAVE_TPCSHRD_H "1" CACHE INTERNAL "Have include tpcshrd.h")
set(HAVE_WIN32_CC "1" CACHE INTERNAL "Test HAVE_WIN32_CC")
set(HAVE_XINPUT_H "1" CACHE INTERNAL "Test HAVE_XINPUT_H")
diff --git a/include/SDL3/SDL_test_common.h b/include/SDL3/SDL_test_common.h
index 91efe8ac34301..5f8a1ffea5dbc 100644
--- a/include/SDL3/SDL_test_common.h
+++ b/include/SDL3/SDL_test_common.h
@@ -87,6 +87,8 @@ typedef struct
const char *window_icon;
SDL_WindowFlags window_flags;
bool flash_on_focus_loss;
+ SDL_ProgressState progress_state;
+ float progress_value;
int window_x;
int window_y;
int window_w;
diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h
index 2a46a5925c2b5..3bebf9093f17d 100644
--- a/include/SDL3/SDL_video.h
+++ b/include/SDL3/SDL_video.h
@@ -307,6 +307,20 @@ typedef enum SDL_FlashOperation
SDL_FLASH_UNTIL_FOCUSED /**< Flash the window until it gets focus */
} SDL_FlashOperation;
+/**
+ * Window progress state
+ *
+ * \since This enum is available since SDL 3.2.8.
+ */
+typedef enum SDL_ProgressState
+{
+ SDL_PROGRESS_STATE_NONE, /**< No progress bar is shown */
+ SDL_PROGRESS_STATE_INDETERMINATE, /**< The progress bar is shown in a indeterminate state */
+ SDL_PROGRESS_STATE_NORMAL, /**< The progress bar is shown in a normal state */
+ SDL_PROGRESS_STATE_PAUSED, /**< The progress bar is shown in a paused state */
+ SDL_PROGRESS_STATE_ERROR /**< The progress bar is shown in an error state */
+} SDL_ProgressState;
+
/**
* An opaque handle to an OpenGL context.
*
@@ -2806,6 +2820,34 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowShape(SDL_Window *window, SDL_Surf
*/
extern SDL_DECLSPEC bool SDLCALL SDL_FlashWindow(SDL_Window *window, SDL_FlashOperation operation);
+/**
+ * Sets the state of the progress bar for the given window’s taskbar icon.
+ *
+ * \param window the window whose progress state is to be modified.
+ * \param state the progress state.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should only be called on the main thread.
+ *
+ * \since This function is available since SDL 3.2.8.
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowProgressState(SDL_Window *window, SDL_ProgressState state);
+
+/**
+ * Sets the value of the progress bar for the given window’s taskbar icon.
+ *
+ * \param window the window whose progress value is to be modified.
+ * \param value the progress value (0.0f - start, 1.0f - end).
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should only be called on the main thread.
+ *
+ * \since This function is available since SDL 3.2.8.
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowProgressValue(SDL_Window *window, float value);
+
/**
* Destroy a window.
*
diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake
index 20608b3ae3e94..4b40e281c535f 100644
--- a/include/build_config/SDL_build_config.h.cmake
+++ b/include/build_config/SDL_build_config.h.cmake
@@ -225,6 +225,7 @@
#cmakedefine HAVE_TPCSHRD_H 1
#cmakedefine HAVE_ROAPI_H 1
#cmakedefine HAVE_SHELLSCALINGAPI_H 1
+#cmakedefine HAVE_SHOBJIDL_CORE_H 1
#cmakedefine USE_POSIX_SPAWN 1
diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h
index 4427517641948..24c578b6f88c6 100644
--- a/include/build_config/SDL_build_config_windows.h
+++ b/include/build_config/SDL_build_config_windows.h
@@ -95,6 +95,9 @@ typedef unsigned int uintptr_t;
#if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0603 /* Windows 8.1 SDK */
#define HAVE_SHELLSCALINGAPI_H 1
#endif
+#if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0601 /* Windows 7 SDK */
+#define HAVE_SHOBJIDL_CORE_H 1
+#endif
#define HAVE_MMDEVICEAPI_H 1
#define HAVE_AUDIOCLIENT_H 1
#define HAVE_TPCSHRD_H 1
diff --git a/include/build_config/SDL_build_config_wingdk.h b/include/build_config/SDL_build_config_wingdk.h
index 502eb92994727..f51a2fffb8131 100644
--- a/include/build_config/SDL_build_config_wingdk.h
+++ b/include/build_config/SDL_build_config_wingdk.h
@@ -43,6 +43,7 @@
#define HAVE_D3D11_H 1
#define HAVE_ROAPI_H 1
#define HAVE_SHELLSCALINGAPI_H 1
+#define HAVE_SHOBJIDL_CORE_H 1
#define HAVE_MMDEVICEAPI_H 1
#define HAVE_AUDIOCLIENT_H 1
#define HAVE_TPCSHRD_H 1
diff --git a/include/build_config/SDL_build_config_xbox.h b/include/build_config/SDL_build_config_xbox.h
index 5834e71f6fe64..500468dd50fd2 100644
--- a/include/build_config/SDL_build_config_xbox.h
+++ b/include/build_config/SDL_build_config_xbox.h
@@ -41,6 +41,7 @@
/*#define HAVE_WINDOWS_GAMING_INPUT_H 1*/
/*#define HAVE_ROAPI_H 1*/
/*#define HAVE_SHELLSCALINGAPI_H 1*/
+/*#define HAVE_SHOBJIDL_CORE_H 1*/
#define HAVE_MMDEVICEAPI_H 1
#define HAVE_AUDIOCLIENT_H 1
/*#define HAVE_TPCSHRD_H 1*/
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 8b3c48170e680..8c061ccaed354 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1242,6 +1242,8 @@ SDL3_0.0.0 {
SDL_SetGPURenderStateFragmentUniforms;
SDL_SetRenderGPUState;
SDL_DestroyGPURenderState;
+ SDL_SetWindowProgressState;
+ SDL_SetWindowProgressValue;
# 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 df0848e280c5c..d3f218f52a6dd 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1267,3 +1267,5 @@
#define SDL_SetGPURenderStateFragmentUniforms SDL_SetGPURenderStateFragmentUniforms_REAL
#define SDL_SetRenderGPUState SDL_SetRenderGPUState_REAL
#define SDL_DestroyGPURenderState SDL_DestroyGPURenderState_REAL
+#define SDL_SetWindowProgressState SDL_SetWindowProgressState_REAL
+#define SDL_SetWindowProgressValue SDL_SetWindowProgressValue_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 7be1e52219037..d9c9ab1f4c748 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1275,3 +1275,5 @@ SDL_DYNAPI_PROC(SDL_GPURenderState*,SDL_CreateGPURenderState,(SDL_Renderer *a,SD
SDL_DYNAPI_PROC(bool,SDL_SetGPURenderStateFragmentUniforms,(SDL_GPURenderState *a,Uint32 b,const void *c,Uint32 d),(a,b,c,d),return)
SDL_DYNAPI_PROC(bool,SDL_SetRenderGPUState,(SDL_Renderer *a,SDL_GPURenderState *b),(a,b),return)
SDL_DYNAPI_PROC(void,SDL_DestroyGPURenderState,(SDL_GPURenderState *a),(a),)
+SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressState,(SDL_Window *a,SDL_ProgressState b),(a,b),return)
+SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressValue,(SDL_Window *a,float b),(a,b),return)
diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c
index 315fec06dd299..f1a35376186c2 100644
--- a/src/test/SDL_test_common.c
+++ b/src/test/SDL_test_common.c
@@ -2455,6 +2455,31 @@ SDL_AppResult SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const
}
}
break;
+ case SDLK_P:
+ if (withAlt) {
+ /* Ctrl-P Cycle through progress states */
+ SDL_Window *window = SDL_GetWindowFromEvent(event);
+ if (window) {
+ state->progress_state += 1;
+ if (state->progress_state > SDL_PROGRESS_STATE_ERROR) {
+ state->progress_state = SDL_PROGRESS_STATE_NONE;
+ }
+ SDL_SetWindowProgressState(window, state->progress_state);
+ }
+ }
+ else if (withControl)
+ {
+ /* Alt-P Increase progress value */
+ SDL_Window *window = SDL_GetWindowFromEvent(event);
+ if (window) {
+ state->progress_value += 0.1f;
+ if (state->progress_value > 1.f) {
+ state->progress_value = 0.f;
+ }
+ SDL_SetWindowProgressValue(window, state->progress_value);
+ }
+ }
+ break;
case SDLK_G:
if (withControl) {
/* Ctrl-G toggle mouse grab */
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index cf856b096d100..f9b604c4b7631 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -303,6 +303,8 @@ struct SDL_VideoDevice
void (*OnWindowEnter)(SDL_VideoDevice *_this, SDL_Window *window);
bool (*UpdateWindowShape)(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *shape);
bool (*FlashWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);
+ bool (*SetWindowProgressState)(SDL_VideoDevice *_this, SDL_Window *window, SDL_ProgressState state);
+ bool (*SetWindowProgressValue)(SDL_VideoDevice *_this, SDL_Window *window, float value);
bool (*SetWindowFocusable)(SDL_VideoDevice *_this, SDL_Window *window, bool focusable);
bool (*SyncWindow)(SDL_VideoDevice *_this, SDL_Window *window);
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 78b95014aa326..eb8d434af90d4 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -3921,6 +3921,30 @@ bool SDL_FlashWindow(SDL_Window *window, SDL_FlashOperation operation)
return SDL_Unsupported();
}
+bool SDL_SetWindowProgressState(SDL_Window *window, SDL_ProgressState state)
+{
+ CHECK_WINDOW_MAGIC(window, false);
+ CHECK_WINDOW_NOT_POPUP(window, false);
+
+ if (_this->SetWindowProgressState) {
+ return _this->SetWindowProgressState(_this, window, state);
+ }
+
+ return SDL_Unsupported();
+}
+
+bool SDL_SetWindowProgressValue(SDL_Window *window, float value)
+{
+ CHECK_WINDOW_MAGIC(window, false);
+ CHECK_WINDOW_NOT_POPUP(window, false);
+
+ if (_this->SetWindowProgressValue) {
+ return _this->SetWindowProgressValue(_this, window, value);
+ }
+
+ return SDL_Unsupported();
+}
+
void SDL_OnWindowShown(SDL_Window *window)
{
// Set window state if we have pending window flags cached
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index e6f10480d72fc..6caf32ff50ff3 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -51,6 +51,10 @@
#include "wmmsg.h"
#endif
+#ifdef HAVE_SHOBJIDL_CORE_H
+#include <shobjidl_core.h>
+#endif
+
#ifdef SDL_PLATFORM_GDK
#include "../../core/gdk/SDL_gdk.h"
#endif
@@ -2431,6 +2435,12 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
}
+#ifdef HAVE_SHOBJIDL_CORE_H
+ if (msg == data->videodata->WM_TASKBAR_BUTTON_CREATED) {
+ data->videodata->taskbar_button_created = true;
+ }
+#endif
+
// If there's a window proc, assume it's going to handle messages
if (data->wndproc) {
return CallWindowProc(data->wndproc, hwnd, msg, wParam, lParam);
diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c
index 6103d52334e1d..3319d05ffcc14 100644
--- a/src/video/windows/SDL_windowsvideo.c
+++ b/src/video/windows/SDL_windowsvideo.c
@@ -36,6 +36,10 @@
#include "SDL_windowsrawinput.h"
#include "SDL_windowsvulkan.h"
+#ifdef HAVE_SHOBJIDL_CORE_H
+#include <shobjidl_core.h>
+#endif
+
#ifdef SDL_GDK_TEXTINPUT
#include "../gdk/SDL_gdktextinput.h"
#endif
@@ -268,6 +272,8 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
device->SetWindowHitTest = WIN_SetWindowHitTest;
device->AcceptDragAndDrop = WIN_AcceptDragAndDrop;
device->FlashWindow = WIN_FlashWindow;
+ device->SetWindowProgressState = WIN_SetWindowProgressState;
+ device->SetWindowProgressValue = WIN_SetWindowProgressValue;
device->ShowWindowSystemMenu = WIN_ShowWindowSystemMenu;
device->SetWindowFocusable = WIN_SetWindowFocusable;
device->UpdateWindowShape = WIN_UpdateWindowShape;
@@ -552,6 +558,9 @@ static bool WIN_VideoInit(SDL_VideoDevice *_this)
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
data->_SDL_WAKEUP = RegisterWindowMessageA("_SDL_WAKEUP");
#endif
+#if defined(HAVE_SHOBJIDL_CORE_H)
+ data->WM_TASKBAR_BUTTON_CREATED = RegisterWindowMessageA("TaskbarButtonCreated");
+#endif
return true;
}
@@ -581,6 +590,14 @@ void WIN_VideoQuit(SDL_VideoDevice *_this)
}
#endif // !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
+#if defined(HAVE_SHOBJIDL_CORE_H)
+ data->taskbar_button_created = false;
+ if (data->taskbar_list) {
+ IUnknown_Release(data->taskbar_list);
+ data->taskbar_list = NULL;
+ }
+#endif
+
if (data->coinitialized) {
WIN_CoUninitialize();
data->coinitialized = false;
diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h
index c80a721a83620..4e985bd9b634f 100644
--- a/src/video/windows/SDL_windowsvideo.h
+++ b/src/video/windows/SDL_windowsvideo.h
@@ -308,6 +308,8 @@ typedef enum PROCESS_DPI_AWARENESS
#include <shellscalingapi.h>
#endif
+typedef struct ITaskbarList3 ITaskbarList3;
+
#ifndef _DPI_AWARENESS_CONTEXTS_
typedef enum DPI_AWARENESS
@@ -536,6 +538,12 @@ struct SDL_VideoData
BYTE pre_hook_key_state[256];
UINT _SDL_WAKEUP;
+
+#ifdef HAVE_SHOBJIDL_CORE_H
+ UINT WM_TASKBAR_BUTTON_CREATED;
+ bool taskbar_button_created;
+ ITaskbarList3 *taskbar_list;
+#endif
};
extern bool g_WindowsEnableMessageLoop;
diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c
index cd06d5d9e97f9..c5576574546eb 100644
--- a/src/video/windows/SDL_windowswindow.c
+++ b/src/video/windows/SDL_windowswindow.c
@@ -38,6 +38,10 @@
// Dropfile support
#include <shellapi.h>
+#ifdef HAVE_SHOBJIDL_CORE_H
+#include <shobjidl_core.h>
+#endif
+
// Dark mode support
typedef enum {
UXTHEME_APPMODE_DEFAULT,
@@ -180,6 +184,33 @@ static DWORD GetWindowStyleEx(SDL_Window *window)
return style;
}
+#ifdef HAVE_SHOBJIDL_CORE_H
+static ITaskbarList3 *GetTaskbarList(SDL_Window* window)
+{
+ const SDL_WindowData *data = window->internal;
+ if (!data->videodata->taskbar_button_created) {
+ WIN_SetError("Missing taskbar button");
+ return NULL;
+ }
+ if (!data->videodata->taskbar_list) {
+ HRESULT ret = CoCreateInstance(&CLSID_TaskbarList, NULL, CLSCTX_ALL, &IID_ITaskbarList3, (LPVOID *)&data->videodata->taskbar_list);
+ if (FAILED(ret)) {
+ WIN_SetError("Unable to create taskbar list");
+ return NULL;
+ }
+ ITaskbarList3 *taskbarlist = data->videodata->taskbar_list;
+ ret = taskbarlist->lpVtbl->HrInit(taskbarlist);
+ if (FAILED(ret)) {
+ taskbarlist->lpVtbl->Release(taskbarlist);
+ data->videodata->taskbar_list = NULL;
+ WIN_SetError("Unable to initialize taskbar list");
+ return NULL;
+ }
+ }
+ return data->videodata->taskbar_list;
+}
+#endif
+
/**
* Returns arguments to pass to SetWindowPos - the window rect, including frame, in Windows coordinates.
* Can be called before we have a HWND.
@@ -694,7 +725,7 @@ static void WIN_SetKeyboardFocus(SDL_Window *window, bool set_active_focus)
toplevel->internal->keyboard_focus = window;
if (set_active_focus && !window->is_hiding && !window->is_destroying) {
- SDL_SetKeyboardFocus(window);
+ SDL_SetKeyboardFocus(window);
}
}
@@ -1051,7 +1082,7 @@ void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
}
if (window->flags & SDL_WINDOW_POPUP_MENU && bActivate) {
- WIN_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus());
+ WIN_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus());
}
if (window->flags & SDL_WINDOW_MODAL) {
WIN_SetWindowModal(_this, window, true);
@@ -2217,6 +2248,66 @@ bool WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperat
return true;
}
+bool WIN_SetWindowProgressState(SDL_VideoDevice *_this, SDL_Window *window, SDL_ProgressState state)
+{
+#ifndef HAVE_SHOBJIDL_CORE_H
+ return false;
+#else
+ ITaskbarList3 *taskbar_list = GetTaskbarList(window);
+ if (!taskbar_list) {
+ return false;
+ };
+
+ TBPFLAG tbpFlags;
+ switch (state) {
+ case SDL_PROGRESS_STATE_NONE:
+ tbpFlags = TBPF_NOPROGRESS;
+ break;
+ case SDL_PROGRESS_STATE_INDETERMINATE:
+ tbpFlags = TBPF_INDETERMINATE;
+ break;
+ case SDL_PROGRESS_STATE_NORMAL:
+ tbpFlags = TBPF_NORMAL;
+ break;
+ case SDL_PROGRESS_STATE_PAUSED:
+ tbpFlags = TBPF_PAUSED;
+ break;
+ case SDL_PROGRESS_STATE_ERROR:
+ tbpFlags = TBPF_ERROR;
+ break;
+ default:
+ return SDL_Unsupported();
+ }
+
+ HRESULT ret = taskbar_list->lpVtbl->SetProgressState(taskbar_list, window->internal->hwnd, tbpFlags);
+ if (FAILED(ret)) {
+ return WIN_SetErrorFromHRESULT("ITaskbarList3::SetProgressState()", ret);
+ }
+
+ return true;
+#endif // HAVE_SHOBJIDL_CORE_H
+}
+
+bool WIN_SetWindowProgressValue(SDL_VideoDevice *_this, SDL_Window *window, float value)
+{
+#ifndef HAVE_SHOBJIDL_CORE_H
+ return false;
+#else
+ ITaskbarList3 *taskbar_list = GetTaskbarList(window);
+ if (!taskbar_list) {
+ return false;
+ };
+
+ SDL_clamp(value, 0.0f, 1.f);
+ HRESULT ret = taskbar_list->lpVtbl->SetProgressValue(taskbar_list, window->internal->hwnd, (ULONGLONG)(value * 10000.f), 10000);
+ if (FAILED(ret)) {
+ return WIN_SetErrorFromHRESULT("ITaskbarList3::SetProgressValue()", ret);
+ }
+
+ return true;
+#endif // HAVE_SHOBJIDL_CORE_H
+}
+
void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y)
{
const SDL_WindowData *data = window->internal;
diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h
index 3d1aaed0a24fb..9ec2578f63103 100644
--- a/src/video/windows/SDL_windowswindow.h
+++ b/src/video/windows/SDL_windowswindow.h
@@ -133,6 +133,8 @@ extern void WIN_UnclipCursorForWindow(SDL_Window *window);
extern bool WIN_SetWindowHitTest(SDL_Window *window, bool enabled);
extern void WIN_AcceptDragAndDrop(SDL_Window *window, bool accept);
extern bool WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);
+extern bool WIN_SetWindowProgressState(SDL_VideoDevice *_this, SDL_Window *window, SDL_ProgressState state);
+extern bool WIN_SetWindowProgressValue(SDL_VideoDevice *_this, SDL_Window *window, float value);
extern void WIN_UpdateDarkModeForHWND(HWND hwnd);
extern bool WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRect rect_type);
extern void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y);