55#include < Windows.h>
66#include < Shellapi.h>
77#include < tchar.h>
8+ #include < cstdint>
9+ #include < ctime>
810#include < algorithm>
911#include < cctype>
1012#include < cfloat>
3032#include " hidhide_probe.h"
3133#include " libwdi_probe.h"
3234#include " resource.h"
35+ #include " update_check.h"
3336
3437#define IDM_ABOUT 0xF200
3538#define IDM_PREFERENCES 0xF210
@@ -43,6 +46,8 @@ struct AppPrefs
4346 int windowW = 0 ; // 0 = use default position/size
4447 int windowH = 0 ;
4548 int lastTabIndex = 0 ; // backend tab index to restore on launch
49+ /* * UTC Unix seconds when the user dismissed the update dialog; 0 = never. Suppresses checks for 24h. */
50+ int64_t updateDismissedUnix = 0 ;
4651};
4752
4853/* *
@@ -69,7 +74,7 @@ static std::wstring GetConfigPath()
6974 *
7075 * Reads the config file located in the application's AppData folder and applies recognized keys
7176 * from the [Settings] section into the provided AppPrefs structure. Supported keys:
72- * RefreshRate, VSync, WindowX, WindowY, WindowW, WindowH, LastTabIndex.
77+ * RefreshRate, VSync, WindowX, WindowY, WindowW, WindowH, LastTabIndex, UpdateDismissedUnix .
7378 *
7479 * - If the config file is missing or unreadable, the function leaves prefs unchanged.
7580 * - RefreshRate is accepted only if it equals 0, 60, 75, 120, or 144; other values are ignored.
@@ -143,6 +148,10 @@ static void LoadConfig(AppPrefs& prefs)
143148 {
144149 try { prefs.lastTabIndex = std::stoi (val); } catch (...) {}
145150 }
151+ else if (key == " UpdateDismissedUnix" )
152+ {
153+ try { prefs.updateDismissedUnix = std::stoll (val); } catch (...) {}
154+ }
146155 }
147156 // Treat invalid dimensions as "not set"
148157 if (prefs.windowW <= 0 || prefs.windowH <= 0 )
@@ -160,6 +169,7 @@ static void LoadConfig(AppPrefs& prefs)
160169 * - vsync: vertical sync enabled flag
161170 * - windowX, windowY, windowW, windowH: saved window position and size
162171 * - lastTabIndex: backend tab index to restore on launch
172+ * - updateDismissedUnix: UTC Unix time when update dialog was dismissed
163173 */
164174static void SaveConfig (const AppPrefs& prefs)
165175{
@@ -177,6 +187,7 @@ static void SaveConfig(const AppPrefs& prefs)
177187 f << " WindowW=" << prefs.windowW << " \n " ;
178188 f << " WindowH=" << prefs.windowH << " \n " ;
179189 f << " LastTabIndex=" << prefs.lastTabIndex << " \n " ;
190+ f << " UpdateDismissedUnix=" << prefs.updateDismissedUnix << " \n " ;
180191}
181192
182193/* *
@@ -231,6 +242,11 @@ static std::vector<std::string> g_libwdiUsbInstanceIdsUtf8;
231242static std::string g_libwdiUsbProbeErrorUtf8;
232243static AppPrefs g_prefs;
233244
245+ static std::unique_ptr<UpdateCheckSession> g_updateCheckSession;
246+ static bool g_showUpdateAvailable = false ;
247+ static std::string g_updateLocalVerUtf8;
248+ static std::string g_updateRemoteVerUtf8;
249+
234250static std::string WideToUtf8 (const std::wstring_view w)
235251{
236252 if (w.empty ())
@@ -275,6 +291,18 @@ static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
275291
276292 switch (msg)
277293 {
294+ case WM_UPDATE_CHECK_READY :
295+ {
296+ std::string loc;
297+ std::string rem;
298+ if (UpdateCheck_PopResultForUi (loc, rem))
299+ {
300+ g_updateLocalVerUtf8 = std::move (loc);
301+ g_updateRemoteVerUtf8 = std::move (rem);
302+ g_showUpdateAvailable = true ;
303+ }
304+ }
305+ return 0 ;
278306 case WM_SIZE :
279307 if (g_d3d.device && wParam != SIZE_MINIMIZED )
280308 g_d3d.Resize (lParam);
@@ -294,6 +322,7 @@ static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
294322 return 0 ;
295323 break ;
296324 case WM_DESTROY :
325+ g_updateCheckSession.reset ();
297326 {
298327 RECT r;
299328 if (GetWindowRect (hWnd, &r))
@@ -458,6 +487,14 @@ int APIENTRY wWinMain(
458487 ShowWindow (hwnd, nCmdShow);
459488 UpdateWindow (hwnd);
460489
490+ try
491+ {
492+ g_updateCheckSession = std::make_unique<UpdateCheckSession>(hwnd, g_prefs.updateDismissedUnix );
493+ }
494+ catch (...)
495+ {
496+ }
497+
461498 constexpr float clearColor[4 ] = {0 .06f , 0 .06f , 0 .07f , 1 .0f };
462499
463500 MSG msg{};
@@ -694,6 +731,51 @@ int APIENTRY wWinMain(
694731 ImGui::EndPopup ();
695732 }
696733
734+ const char * const kUpdateAvailablePopupId = " Update available" ;
735+ if (g_showUpdateAvailable)
736+ ImGui::OpenPopup (kUpdateAvailablePopupId );
737+
738+ const bool updatePopupActive =
739+ g_showUpdateAvailable || ImGui::IsPopupOpen (kUpdateAvailablePopupId , ImGuiPopupFlags_None);
740+ if (updatePopupActive)
741+ {
742+ const float updateMinW = 400 .f , updateMinH = 140 .f ;
743+ ImGui::SetNextWindowSizeConstraints (ImVec2 (updateMinW, updateMinH),
744+ ImVec2 (FLT_MAX , FLT_MAX ));
745+ ImGui::SetNextWindowPos (ImGui::GetMainViewport ()->GetCenter (),
746+ ImGuiCond_Appearing, ImVec2 (0 .5f , 0 .5f ));
747+ }
748+ if (ImGui::BeginPopupModal (
749+ kUpdateAvailablePopupId ,
750+ &g_showUpdateAvailable,
751+ ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize))
752+ {
753+ ImGui::TextWrapped (" A newer version of MultiPad Tester is available." );
754+ ImGui::Spacing ();
755+ ImGui::Text (" Installed version: %s" , g_updateLocalVerUtf8.c_str ());
756+ ImGui::Text (" Latest version: %s" , g_updateRemoteVerUtf8.c_str ());
757+ ImGui::Spacing ();
758+ if (ImGui::Button (" Download update" , ImVec2 (140 , 0 )))
759+ {
760+ ShellExecuteW (
761+ nullptr ,
762+ L" open" ,
763+ UpdateCheck_GetLatestDownloadUrlW (),
764+ nullptr ,
765+ nullptr ,
766+ SW_SHOWNORMAL );
767+ }
768+ ImGui::SameLine ();
769+ if (ImGui::Button (" Not today" , ImVec2 (130 , 0 )))
770+ {
771+ g_prefs.updateDismissedUnix = static_cast <int64_t >(std::time (nullptr ));
772+ SaveConfig (g_prefs);
773+ ImGui::CloseCurrentPopup ();
774+ g_showUpdateAvailable = false ;
775+ }
776+ ImGui::EndPopup ();
777+ }
778+
697779 const char * const kHidHideActivePopupId = " HidHide Active Warning" ;
698780 if (g_showHidHideWarning)
699781 ImGui::OpenPopup (kHidHideActivePopupId );
@@ -834,6 +916,8 @@ int APIENTRY wWinMain(
834916 g_d3d.Present (g_prefs.vsync );
835917 }
836918
919+ g_updateCheckSession.reset ();
920+
837921 g_backends = nullptr ;
838922
839923 ImGui_ImplDX11_Shutdown ();
0 commit comments