Skip to content

Instantly share code, notes, and snippets.

@shinmai
Last active March 15, 2025 12:19
Show Gist options
  • Save shinmai/11a7d4d4debe87117996ac62c24181a3 to your computer and use it in GitHub Desktop.
Save shinmai/11a7d4d4debe87117996ac62c24181a3 to your computer and use it in GitHub Desktop.
lightweight Win32 temperature monitor using LibreHardwareMonitor and lhwm-wrapper
/*
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