Skip to content

Commit d3e23e7

Browse files
committed
add version check system and notification UI to menu
1 parent d7c7565 commit d3e23e7

2 files changed

Lines changed: 209 additions & 4 deletions

File tree

UnityInspector/src/menu/menu.cpp

Lines changed: 204 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,187 @@ namespace Menu
1010
static std::vector<std::unique_ptr<ITab>> s_Tabs;
1111
static bool s_Initialized = false;
1212

13+
static std::atomic s_UpdateCheckFinished = false;
14+
static bool s_HasUpdate = false;
15+
static std::string s_LatestVersion = "";
16+
static std::string s_ReleaseUrl = "";
17+
static std::string s_UpdateError = "";
18+
19+
static std::string FetchLatestReleaseJson(std::string& outError)
20+
{
21+
std::string responseData;
22+
HINTERNET hSession = nullptr, hConnect = nullptr, hRequest = nullptr;
23+
24+
hSession = WinHttpOpen(L"UnityInspectorStandalone/1.0",
25+
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
26+
nullptr,
27+
nullptr, 0);
28+
if (!hSession)
29+
{
30+
outError = "WinHttpOpen failed: " + std::to_string(GetLastError());
31+
return "";
32+
}
33+
34+
WinHttpSetTimeouts(hSession, 5000, 5000, 5000, 5000);
35+
36+
hConnect = WinHttpConnect(hSession, L"api.github.com", INTERNET_DEFAULT_HTTPS_PORT, 0);
37+
if (!hConnect)
38+
{
39+
outError = "WinHttpConnect failed: " + std::to_string(GetLastError());
40+
WinHttpCloseHandle(hSession);
41+
return "";
42+
}
43+
44+
hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/repos/PicoShot/UnityInspectorStandalone/releases/latest",
45+
nullptr, nullptr,
46+
nullptr,
47+
WINHTTP_FLAG_SECURE);
48+
if (!hRequest)
49+
{
50+
outError = "WinHttpOpenRequest failed: " + std::to_string(GetLastError());
51+
WinHttpCloseHandle(hConnect);
52+
WinHttpCloseHandle(hSession);
53+
return "";
54+
}
55+
56+
BOOL bResults = WinHttpSendRequest(hRequest,
57+
nullptr, 0,
58+
nullptr, 0,
59+
0, 0);
60+
61+
if (!bResults)
62+
{
63+
outError = "WinHttpSendRequest failed: " + std::to_string(GetLastError());
64+
}
65+
else
66+
{
67+
bResults = WinHttpReceiveResponse(hRequest, nullptr);
68+
if (!bResults)
69+
{
70+
outError = "WinHttpReceiveResponse failed: " + std::to_string(GetLastError());
71+
}
72+
}
73+
74+
if (bResults)
75+
{
76+
DWORD dwSize = 0;
77+
do
78+
{
79+
dwSize = 0;
80+
if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
81+
{
82+
outError = "WinHttpQueryDataAvailable failed: " + std::to_string(GetLastError());
83+
break;
84+
}
85+
if (dwSize == 0) break;
86+
87+
std::vector<char> tempBuffer(dwSize);
88+
DWORD dwDownloaded = 0;
89+
if (!WinHttpReadData(hRequest, tempBuffer.data(), dwSize, &dwDownloaded))
90+
{
91+
outError = "WinHttpReadData failed: " + std::to_string(GetLastError());
92+
break;
93+
}
94+
responseData.append(tempBuffer.data(), dwDownloaded);
95+
}
96+
while (dwSize > 0);
97+
}
98+
99+
if (hRequest) WinHttpCloseHandle(hRequest);
100+
if (hConnect) WinHttpCloseHandle(hConnect);
101+
if (hSession) WinHttpCloseHandle(hSession);
102+
103+
return responseData;
104+
}
105+
106+
static std::vector<int> ParseVersion(const std::string& verStr)
107+
{
108+
std::vector<int> parts;
109+
std::string current;
110+
for (char c : verStr)
111+
{
112+
if (std::isdigit(static_cast<unsigned char>(c)))
113+
{
114+
current += c;
115+
}
116+
else if (c == '.' || c == '-')
117+
{
118+
if (!current.empty())
119+
{
120+
parts.push_back(std::stoi(current));
121+
current.clear();
122+
}
123+
if (c == '-') break;
124+
}
125+
}
126+
if (!current.empty())
127+
{
128+
parts.push_back(std::stoi(current));
129+
}
130+
return parts;
131+
}
132+
133+
static bool IsNewerVersion(const std::string& latest, const std::string& current)
134+
{
135+
auto latest_parts = ParseVersion(latest);
136+
auto current_parts = ParseVersion(current);
137+
for (size_t i = 0; i < std::max(latest_parts.size(), current_parts.size()); ++i)
138+
{
139+
int l = (i < latest_parts.size()) ? latest_parts[i] : 0;
140+
int c = (i < current_parts.size()) ? current_parts[i] : 0;
141+
if (l > c) return true;
142+
if (l < c) return false;
143+
}
144+
return false;
145+
}
146+
13147
void Init()
14148
{
15149
if (s_Initialized) return;
16150
s_Tabs.push_back(std::make_unique<DebugTab>());
17151
s_Tabs.push_back(std::make_unique<LuaConsoleTab>());
18152
s_Tabs.push_back(std::make_unique<MiscTab>());
19153
s_Initialized = true;
154+
155+
std::thread([]
156+
{
157+
std::string err;
158+
std::string jsonStr = FetchLatestReleaseJson(err);
159+
if (!err.empty())
160+
{
161+
s_UpdateError = err;
162+
s_UpdateCheckFinished = true;
163+
return;
164+
}
165+
166+
try
167+
{
168+
if (Json json = Json::parse(jsonStr); json.contains("tag_name") && json["tag_name"].is_string())
169+
{
170+
std::string latestTag = json["tag_name"];
171+
s_LatestVersion = latestTag;
172+
173+
if (json.contains("html_url") && json["html_url"].is_string())
174+
{
175+
s_ReleaseUrl = json["html_url"];
176+
}
177+
else
178+
{
179+
s_ReleaseUrl = "https://github.com/PicoShot/UnityInspectorStandalone/releases";
180+
}
181+
182+
if (IsNewerVersion(latestTag, VERSION))
183+
{
184+
s_HasUpdate = true;
185+
}
186+
}
187+
}
188+
catch (const std::exception& e)
189+
{
190+
s_UpdateError = std::string("JSON parse error: ") + e.what();
191+
}
192+
s_UpdateCheckFinished = true;
193+
}).detach();
20194
}
21195

22196
void Render()
@@ -28,15 +202,41 @@ namespace Menu
28202
ImGui::SetNextWindowSize(ImVec2(550, 350), ImGuiCond_FirstUseEver);
29203
ImGui::SetNextWindowPos(ImVec2(600, 400), ImGuiCond_FirstUseEver);
30204

205+
std::string menuTitle = "UnityInspector v" VERSION;
31206
#ifdef _DEBUG
32-
const auto menuTitle = "UnityInspector (Debug)";
33-
#else
34-
const auto menuTitle = "UnityInspector (Release)";
207+
menuTitle += " (Debug)";
35208
#endif
36209

37-
ImGui::Begin(menuTitle, &Config::state.showMenu,
210+
ImGui::Begin(menuTitle.c_str(), &Config::state.showMenu,
38211
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize);
39212

213+
if (s_UpdateCheckFinished && s_HasUpdate)
214+
{
215+
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.26f, 0.59f, 0.98f, 0.20f));
216+
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f);
217+
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 6.0f));
218+
219+
if (ImGui::BeginChild("UpdateBanner", ImVec2(0, 34),
220+
ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding))
221+
{
222+
ImGui::AlignTextToFramePadding();
223+
ImGui::Text("A new version (%s) is available!", s_LatestVersion.c_str());
224+
ImGui::SameLine();
225+
226+
float button_width = 80.0f;
227+
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - button_width - 8.0f);
228+
if (ImGui::Button("Update", ImVec2(button_width, 0)))
229+
{
230+
ShellExecuteA(nullptr, "open", s_ReleaseUrl.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
231+
}
232+
}
233+
ImGui::EndChild();
234+
235+
ImGui::PopStyleVar(2);
236+
ImGui::PopStyleColor();
237+
ImGui::Spacing();
238+
}
239+
40240
if (ImGui::BeginTabBar("MainTabs", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton))
41241
{
42242
for (const auto& tab : s_Tabs)

UnityInspector/src/pch/pch.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
// Defines
4+
#define VERSION "0.6.4"
45
#define WIN32_LEAN_AND_MEAN
56
#define NOMINMAX
67
#define WINDOWS_MODE 1
@@ -10,7 +11,11 @@
1011
#include <windows.h>
1112
#include <excpt.h>
1213
#include <dwmapi.h>
14+
#include <winhttp.h>
15+
#include <shellapi.h>
1316
#pragma comment(lib, "dwmapi")
17+
#pragma comment(lib, "winhttp.lib")
18+
#pragma comment(lib, "shell32.lib")
1419

1520
// std
1621
#include <cstdio>

0 commit comments

Comments
 (0)