Last active
March 15, 2025 12:19
-
-
Save shinmai/11a7d4d4debe87117996ac62c24181a3 to your computer and use it in GitHub Desktop.
lightweight Win32 temperature monitor using LibreHardwareMonitor and lhwm-wrapper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Requires: | |
1. LibreHardwareMonitorLib.dll from https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/ | |
2. lhwm-wrapper.dll, lhwm-cpp-wrapper.lib & lhwm-cpp-wrapper.h from https://gitlab.com/OpenRGBDevelopers/OpenRGBHardwareSyncPlugin/-/tree/master/dependencies/lhwm-cpp-wrapper/ | |
Link with lhwm-cpp-wrapper.lib, dwmapi.lib & mscoree.lib from .NET Framework 4.x | |
No config, manually adjust location, text size & style, monitored values and update delay in UpdateOverlay() and the code for the update thread in WinMain() | |
*/ | |
#define WIN32_LEAN_AND_MEAN | |
#include <windows.h> | |
#include <shellapi.h> | |
#include <thread> | |
#include <sstream> | |
#include <iomanip> | |
#include <dwmapi.h> | |
#include "lhwm-cpp-wrapper.h" | |
NOTIFYICONDATA nid = {}; | |
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { | |
switch(uMsg) { | |
case WM_APP + 1: | |
if (LOWORD(lParam) == WM_RBUTTONUP) { | |
HMENU hMenu = CreatePopupMenu(); | |
AppendMenu(hMenu, MF_STRING, 1, L"Exit"); | |
POINT pt; | |
GetCursorPos(&pt); | |
SetForegroundWindow(hwnd); | |
int cmd = TrackPopupMenu(hMenu, TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, 0, hwnd, nullptr); | |
if (cmd == 1) { | |
Shell_NotifyIcon(NIM_DELETE, &nid); | |
std::exit(0); | |
} | |
DestroyMenu(hMenu); | |
} | |
break; | |
case WM_DESTROY: | |
std::exit(0); | |
break; | |
} | |
return DefWindowProc(hwnd, uMsg, wParam, lParam); | |
} | |
void UpdateOverlay(HWND hwnd, const std::wstring& text) { | |
HDC hdc = GetDC(hwnd); | |
RECT rect; | |
GetClientRect(hwnd, &rect); | |
FillRect(hdc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH)); | |
HFONT hFont = CreateFont(17, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH, L"Arial"); | |
SelectObject(hdc, hFont); | |
SetTextColor(hdc, RGB(0, 0, 0)); | |
SetBkMode(hdc, TRANSPARENT); | |
RECT shadowRect = rect; | |
shadowRect.left += 1; | |
shadowRect.top += 1; | |
DrawText(hdc, text.c_str(), -1, &shadowRect, DT_LEFT | DT_TOP); | |
SetTextColor(hdc, RGB(255, 255, 255)); | |
DrawText(hdc, text.c_str(), -1, &rect, DT_LEFT | DT_TOP); | |
DeleteObject(hFont); | |
ReleaseDC(hwnd, hdc); | |
} | |
std::wstring FormatFloat(float value, int precision = 2) { | |
std::wostringstream oss; | |
oss << std::fixed << std::setprecision(precision) << value; | |
return oss.str(); | |
} | |
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) { | |
const wchar_t CLASS_NAME[] = L"OverlayWindowClass"; | |
WNDCLASS wc = { }; | |
wc.lpfnWndProc = WindowProc; | |
wc.hInstance = hInstance; | |
wc.lpszClassName = CLASS_NAME; | |
wc.hbrBackground = (HBRUSH)GetStockObject(HOLLOW_BRUSH); | |
RegisterClass(&wc); | |
HWND hwnd = CreateWindowEx( | |
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, | |
CLASS_NAME, | |
L"temps", | |
WS_POPUP, | |
5, 75, 300, 150, | |
nullptr, nullptr, hInstance, nullptr | |
); | |
SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0, LWA_COLORKEY); | |
ShowWindow(hwnd, SW_SHOW); | |
SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); | |
BOOL excludeFromPeek = TRUE; | |
DwmSetWindowAttribute(hwnd, DWMWA_EXCLUDED_FROM_PEEK, &excludeFromPeek, sizeof(excludeFromPeek)); | |
nid.cbSize = sizeof(NOTIFYICONDATA); | |
nid.hWnd = hwnd; | |
nid.uID = 1; | |
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; | |
nid.uCallbackMessage = WM_APP + 1; | |
nid.hIcon = LoadIcon(nullptr, IDI_APPLICATION); | |
wcscpy_s(nid.szTip, L"temps"); | |
Shell_NotifyIcon(NIM_ADD, &nid); | |
std::thread overlayThread([hwnd]() { | |
do { | |
UpdateOverlay(hwnd, | |
L"CPU: " + FormatFloat(LHWM::GetSensorValue("/amdcpu/0/temperature/3")) + L"°C " + | |
L"(" + FormatFloat(LHWM::GetSensorValue("/amdcpu/0/temperature/2")) + L"°C offset)\n" + | |
L"CPU core voltage: " + FormatFloat(LHWM::GetSensorValue("/amdcpu/0/voltage/0"), 4) + L"V)\n" + | |
L"max core load: " + FormatFloat(LHWM::GetSensorValue("/amdcpu/0/load/1"), 0) + L"% " + | |
L"(" + FormatFloat(LHWM::GetSensorValue("/amdcpu/0/load/0"), 0) + L"% load total)\n" + | |
L"SSD: " + FormatFloat(LHWM::GetSensorValue("/ssd/2/temperature/0"), 0) + L"°C " + | |
L"(" + FormatFloat(LHWM::GetSensorValue("/ssd/2/level/0"), 0) + L"% remaining life)\n" + | |
L"HDDs: #1 " + FormatFloat(LHWM::GetSensorValue("/hdd/0/temperature/0"), 0) + L"°C - " + | |
L"#2 " + FormatFloat(LHWM::GetSensorValue("/hdd/1/temperature/0"), 0) + L"°C\n" + | |
L"GPU: " + FormatFloat(LHWM::GetSensorValue("/gpu-nvidia/0/temperature/0")) + L"°C " + | |
L"(" + FormatFloat(LHWM::GetSensorValue("/gpu-nvidia/0/temperature/2")) + L"°C hot spot)" | |
); | |
std::this_thread::sleep_for(std::chrono::seconds(7)); | |
} while (true); | |
PostQuitMessage(0); | |
}); | |
MSG msg = { }; | |
while (GetMessage(&msg, nullptr, 0, 0)) { | |
TranslateMessage(&msg); | |
DispatchMessage(&msg); | |
} | |
overlayThread.join(); | |
Shell_NotifyIcon(NIM_DELETE, &nid); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment