Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ target_link_libraries(MultiPadTester PRIVATE
dxguid.lib
setupapi.lib
runtimeobject.lib
winhttp.lib
version.lib
)

target_compile_definitions(MultiPadTester PRIVATE
Expand Down
86 changes: 85 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <Windows.h>
#include <Shellapi.h>
#include <tchar.h>
#include <cstdint>
#include <ctime>
#include <algorithm>
#include <cctype>
#include <cfloat>
Expand All @@ -30,6 +32,7 @@
#include "hidhide_probe.h"
#include "libwdi_probe.h"
#include "resource.h"
#include "update_check.h"

#define IDM_ABOUT 0xF200
#define IDM_PREFERENCES 0xF210
Expand All @@ -43,6 +46,8 @@ struct AppPrefs
int windowW = 0; // 0 = use default position/size
int windowH = 0;
int lastTabIndex = 0; // backend tab index to restore on launch
/** UTC Unix seconds when the user dismissed the update dialog; 0 = never. Suppresses checks for 24h. */
int64_t updateDismissedUnix = 0;
};

/**
Expand All @@ -69,7 +74,7 @@ static std::wstring GetConfigPath()
*
* Reads the config file located in the application's AppData folder and applies recognized keys
* from the [Settings] section into the provided AppPrefs structure. Supported keys:
* RefreshRate, VSync, WindowX, WindowY, WindowW, WindowH, LastTabIndex.
* RefreshRate, VSync, WindowX, WindowY, WindowW, WindowH, LastTabIndex, UpdateDismissedUnix.
*
* - If the config file is missing or unreadable, the function leaves prefs unchanged.
* - RefreshRate is accepted only if it equals 0, 60, 75, 120, or 144; other values are ignored.
Expand Down Expand Up @@ -143,6 +148,10 @@ static void LoadConfig(AppPrefs& prefs)
{
try { prefs.lastTabIndex = std::stoi(val); } catch (...) {}
}
else if (key == "UpdateDismissedUnix")
{
try { prefs.updateDismissedUnix = std::stoll(val); } catch (...) {}
}
}
// Treat invalid dimensions as "not set"
if (prefs.windowW <= 0 || prefs.windowH <= 0)
Expand All @@ -160,6 +169,7 @@ static void LoadConfig(AppPrefs& prefs)
* - vsync: vertical sync enabled flag
* - windowX, windowY, windowW, windowH: saved window position and size
* - lastTabIndex: backend tab index to restore on launch
* - updateDismissedUnix: UTC Unix time when update dialog was dismissed
*/
static void SaveConfig(const AppPrefs& prefs)
{
Expand All @@ -177,6 +187,7 @@ static void SaveConfig(const AppPrefs& prefs)
f << "WindowW=" << prefs.windowW << "\n";
f << "WindowH=" << prefs.windowH << "\n";
f << "LastTabIndex=" << prefs.lastTabIndex << "\n";
f << "UpdateDismissedUnix=" << prefs.updateDismissedUnix << "\n";
}

/**
Expand Down Expand Up @@ -231,6 +242,11 @@ static std::vector<std::string> g_libwdiUsbInstanceIdsUtf8;
static std::string g_libwdiUsbProbeErrorUtf8;
static AppPrefs g_prefs;

static std::unique_ptr<UpdateCheckSession> g_updateCheckSession;
static bool g_showUpdateAvailable = false;
static std::string g_updateLocalVerUtf8;
static std::string g_updateRemoteVerUtf8;

static std::string WideToUtf8(const std::wstring_view w)
{
if (w.empty())
Expand Down Expand Up @@ -275,6 +291,18 @@ static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

switch (msg)
{
case WM_UPDATE_CHECK_READY:
{
std::string loc;
std::string rem;
if (UpdateCheck_PopResultForUi(loc, rem))
{
g_updateLocalVerUtf8 = std::move(loc);
g_updateRemoteVerUtf8 = std::move(rem);
g_showUpdateAvailable = true;
}
}
return 0;
case WM_SIZE:
if (g_d3d.device && wParam != SIZE_MINIMIZED)
g_d3d.Resize(lParam);
Expand All @@ -294,6 +322,7 @@ static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
return 0;
break;
case WM_DESTROY:
g_updateCheckSession.reset();
{
RECT r;
if (GetWindowRect(hWnd, &r))
Expand Down Expand Up @@ -458,6 +487,14 @@ int APIENTRY wWinMain(
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

try
{
g_updateCheckSession = std::make_unique<UpdateCheckSession>(hwnd, g_prefs.updateDismissedUnix);
}
catch (...)
{
}

constexpr float clearColor[4] = {0.06f, 0.06f, 0.07f, 1.0f};

MSG msg{};
Expand Down Expand Up @@ -694,6 +731,51 @@ int APIENTRY wWinMain(
ImGui::EndPopup();
}

const char* const kUpdateAvailablePopupId = "Update available";
if (g_showUpdateAvailable)
ImGui::OpenPopup(kUpdateAvailablePopupId);

const bool updatePopupActive =
g_showUpdateAvailable || ImGui::IsPopupOpen(kUpdateAvailablePopupId, ImGuiPopupFlags_None);
if (updatePopupActive)
{
const float updateMinW = 400.f, updateMinH = 140.f;
ImGui::SetNextWindowSizeConstraints(ImVec2(updateMinW, updateMinH),
ImVec2(FLT_MAX, FLT_MAX));
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
}
if (ImGui::BeginPopupModal(
kUpdateAvailablePopupId,
&g_showUpdateAvailable,
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize))
Comment on lines +748 to +751

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The modal has a dismissal path that doesn't persist suppression.

Only the "Not today" button writes updateDismissedUnix, but BeginPopupModal(..., &g_showUpdateAvailable, ...) automatically provides a close button. When users click that X button, the modal closes without recording the 24h suppression, so the notification may reappear immediately on the next launch. Either remove the close button parameter here or treat any close as "Not today".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.cpp` around lines 744 - 747, The modal uses
ImGui::BeginPopupModal(kUpdateAvailablePopupId, &g_showUpdateAvailable, ...) so
clicking the built-in close (X) only clears g_showUpdateAvailable and does not
persist the 24h suppression; updateDismissedUnix is only written by the "Not
today" button. Fix by either removing the address parameter (call
BeginPopupModal without the bool pointer) so the built-in close is not shown, or
detect any dismissal via g_showUpdateAvailable becoming false and set
updateDismissedUnix the same way the "Not today" button does; update the logic
around ImGui::BeginPopupModal, kUpdateAvailablePopupId, g_showUpdateAvailable,
and the updateDismissedUnix write site so all close paths persist the 24h
suppression.

{
ImGui::TextWrapped("A newer version of MultiPad Tester is available.");
ImGui::Spacing();
ImGui::Text("Installed version: %s", g_updateLocalVerUtf8.c_str());
ImGui::Text("Latest version: %s", g_updateRemoteVerUtf8.c_str());
ImGui::Spacing();
if (ImGui::Button("Download update", ImVec2(140, 0)))
{
ShellExecuteW(
nullptr,
L"open",
UpdateCheck_GetLatestDownloadUrlW(),
nullptr,
nullptr,
SW_SHOWNORMAL);
}
ImGui::SameLine();
if (ImGui::Button("Not today", ImVec2(130, 0)))
{
g_prefs.updateDismissedUnix = static_cast<int64_t>(std::time(nullptr));
SaveConfig(g_prefs);
ImGui::CloseCurrentPopup();
g_showUpdateAvailable = false;
}
ImGui::EndPopup();
}

const char* const kHidHideActivePopupId = "HidHide Active Warning";
if (g_showHidHideWarning)
ImGui::OpenPopup(kHidHideActivePopupId);
Expand Down Expand Up @@ -834,6 +916,8 @@ int APIENTRY wWinMain(
g_d3d.Present(g_prefs.vsync);
}

g_updateCheckSession.reset();

g_backends = nullptr;

ImGui_ImplDX11_Shutdown();
Expand Down
Loading
Loading