Skip to content

Commit b6f9529

Browse files
authored
Add mod: CrystalDiskInfo Smart Auto-Refresh (#4057)
* Create crystaldiskinfo-smart-auto-refresh.wh.cpp * Update process inclusion
1 parent acfafad commit b6f9529

1 file changed

Lines changed: 188 additions & 0 deletions

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// ==WindhawkMod==
2+
// @id crystaldiskinfo-smart-auto-refresh
3+
// @name CrystalDiskInfo Smart Auto-Refresh
4+
// @description Temporarily pauses the Auto-Refresh function whenever the application's main window is visible
5+
// @version 1.0
6+
// @author Kitsune
7+
// @github https://github.com/AromaKitsune
8+
// @include DiskInfo32.exe
9+
// @include DiskInfo64.exe
10+
// @include DiskInfoA64.exe
11+
// @include DiskInfo32A.exe
12+
// @include DiskInfo64A.exe
13+
// @include DiskInfoA64A.exe
14+
// @include DiskInfo32K.exe
15+
// @include DiskInfo64K.exe
16+
// @include DiskInfoA64K.exe
17+
// @include DiskInfo32S.exe
18+
// @include DiskInfo64S.exe
19+
// @include DiskInfoA64S.exe
20+
// @compilerOptions -lcomctl32
21+
// ==/WindhawkMod==
22+
23+
// ==WindhawkModReadme==
24+
/*
25+
# CrystalDiskInfo Smart Auto-Refresh
26+
CrystalDiskInfo includes an optional Auto-Refresh feature that updates disk
27+
information at specified intervals, which is useful for background monitoring
28+
while the application is minimized to the system tray. However, when the
29+
application's main window is visible, automatic refreshes can interfere with the
30+
analysis of current disk status, as shifting attribute values make tracking
31+
specific metrics more difficult.
32+
33+
This mod temporarily pauses the Auto-Refresh function by blocking its disk
34+
polling cycle whenever the application's main window is visible, allowing for an
35+
uninterrupted analysis of current disk status.
36+
37+
The Auto-Refresh function resumes once the application is minimized to the
38+
taskbar or system tray.
39+
40+
**Note:** If CrystalDiskInfo is already running when the mod is loaded, pick one
41+
of the following options to activate it:
42+
* Restart the application completely.
43+
* Select an interval in **Function** → **Auto Refresh**, including the one
44+
currently set.
45+
*/
46+
// ==/WindhawkModReadme==
47+
48+
#include <windhawk_utils.h>
49+
#include <windows.h>
50+
#include <commctrl.h>
51+
#include <mutex>
52+
#include <unordered_map>
53+
#include <unordered_set>
54+
#include <vector>
55+
56+
std::mutex g_timerMutex;
57+
std::unordered_map<HWND, std::unordered_set<UINT_PTR>> g_timerMap;
58+
59+
// Subclass procedure
60+
LRESULT CALLBACK AutoRefreshSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam,
61+
LPARAM lParam, DWORD_PTR dwRefData)
62+
{
63+
if (uMsg == WM_TIMER)
64+
{
65+
bool isAutoRefreshTimer = false;
66+
{
67+
std::lock_guard<std::mutex> lock(g_timerMutex);
68+
auto it = g_timerMap.find(hWnd);
69+
if (it != g_timerMap.end() && it->second.count(wParam))
70+
{
71+
isAutoRefreshTimer = true;
72+
}
73+
}
74+
75+
if (isAutoRefreshTimer)
76+
{
77+
HWND hRootWnd = GetAncestor(hWnd, GA_ROOT);
78+
if (!hRootWnd)
79+
{
80+
hRootWnd = hWnd;
81+
}
82+
83+
if (IsWindowVisible(hRootWnd) && !IsIconic(hRootWnd))
84+
{
85+
return 0;
86+
}
87+
}
88+
}
89+
else if (uMsg == WM_NCDESTROY)
90+
{
91+
WindhawkUtils::RemoveWindowSubclassFromAnyThread(hWnd,
92+
AutoRefreshSubclassProc);
93+
std::lock_guard<std::mutex> lock(g_timerMutex);
94+
g_timerMap.erase(hWnd);
95+
}
96+
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
97+
}
98+
99+
// Hook for SetTimer
100+
using SetTimer_t = decltype(&SetTimer);
101+
SetTimer_t SetTimer_Original;
102+
UINT_PTR WINAPI SetTimer_Hook(HWND hWnd, UINT_PTR nIDEvent, UINT uElapse,
103+
TIMERPROC lpTimerFunc)
104+
{
105+
// CrystalDiskInfo's minimum Auto-Refresh interval is 1 minute
106+
// This threshold avoids interfering with unrelated functions by filtering
107+
// out high-frequency timers, ensuring only the application's disk polling
108+
// cycle is targeted.
109+
if (hWnd && uElapse >= 60000)
110+
{
111+
bool isSubclassRequired = false;
112+
{
113+
std::lock_guard<std::mutex> lock(g_timerMutex);
114+
auto [it, inserted] = g_timerMap.try_emplace(hWnd);
115+
if (inserted)
116+
{
117+
isSubclassRequired = true;
118+
}
119+
it->second.insert(nIDEvent);
120+
}
121+
122+
if (isSubclassRequired)
123+
{
124+
WindhawkUtils::SetWindowSubclassFromAnyThread(hWnd,
125+
AutoRefreshSubclassProc, 0);
126+
}
127+
}
128+
return SetTimer_Original(hWnd, nIDEvent, uElapse, lpTimerFunc);
129+
}
130+
131+
// Hook for KillTimer
132+
using KillTimer_t = decltype(&KillTimer);
133+
KillTimer_t KillTimer_Original;
134+
BOOL WINAPI KillTimer_Hook(HWND hWnd, UINT_PTR nIDEvent)
135+
{
136+
if (hWnd)
137+
{
138+
std::lock_guard<std::mutex> lock(g_timerMutex);
139+
auto it = g_timerMap.find(hWnd);
140+
if (it != g_timerMap.end())
141+
{
142+
it->second.erase(nIDEvent);
143+
}
144+
}
145+
return KillTimer_Original(hWnd, nIDEvent);
146+
}
147+
148+
// Mod initialization
149+
BOOL Wh_ModInit()
150+
{
151+
Wh_Log(L"Init");
152+
153+
WindhawkUtils::SetFunctionHook(
154+
SetTimer,
155+
SetTimer_Hook,
156+
&SetTimer_Original
157+
);
158+
159+
WindhawkUtils::SetFunctionHook(
160+
KillTimer,
161+
KillTimer_Hook,
162+
&KillTimer_Original
163+
);
164+
165+
return TRUE;
166+
}
167+
168+
// Mod uninitialization
169+
void Wh_ModUninit()
170+
{
171+
Wh_Log(L"Uninit");
172+
173+
std::vector<HWND> windowsToDetach;
174+
{
175+
std::lock_guard<std::mutex> lock(g_timerMutex);
176+
for (const auto& timerEntry : g_timerMap)
177+
{
178+
windowsToDetach.push_back(timerEntry.first);
179+
}
180+
g_timerMap.clear();
181+
}
182+
183+
for (HWND hWnd : windowsToDetach)
184+
{
185+
WindhawkUtils::RemoveWindowSubclassFromAnyThread(hWnd,
186+
AutoRefreshSubclassProc);
187+
}
188+
}

0 commit comments

Comments
 (0)