diff --git a/CMakeLists.txt b/CMakeLists.txt index ed3a2d2ef1..03f7e46ff7 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 #include diff --git a/cmake/PreseedMSVCCache.cmake b/cmake/PreseedMSVCCache.cmake index 17495aa480..23025eb0cb 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 91efe8ac34..5f8a1ffea5 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 2a46a5925c..3bebf9093f 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 20608b3ae3..4b40e281c5 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 4427517641..24c578b6f8 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 502eb92994..f51a2fffb8 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 5834e71f6f..500468dd50 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 8b3c48170e..8c061ccaed 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 df0848e280..d3f218f52a 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 7be1e52219..d9c9ab1f4c 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 315fec06dd..f1a3537618 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 cf856b096d..f9b604c4b7 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 78b95014aa..eb8d434af9 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 e6f10480d7..6caf32ff50 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 +#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 6103d52334..3319d05ffc 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 +#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 c80a721a83..4e985bd9b6 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 #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 cd06d5d9e9..c557657454 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -38,6 +38,10 @@ // Dropfile support #include +#ifdef HAVE_SHOBJIDL_CORE_H +#include +#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 3d1aaed0a2..9ec2578f63 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);