Per-monitor DPI awareness for the Windows host (dpi-23, 7 October 2026, PC 2's 5120 by 2160 at 200 percent rendered stretched and blurry): host.manifest declares PerMonitorV2 (and true/pm), embedded by host.rc as RT_MANIFEST with the link at /MANIFEST:NO; SetProcessDpiAwarenessContext at start as the runtime path for a Windows that ignores the manifest, SetProcessDPIAware as the last fallback; WM_DPICHANGED takes the suggested rectangle so WebView2 re-renders at the new monitor's scale; the opening-size rule now runs in logical pixels (opening_size.h igneum_opening_size_scaled: the primary monitor's physical work area and its DPI from GetDpiForMonitor, looked up at run time, LOGPIXELSX as the fallback), so the 1440 by 900 and 1920 caps mean CSS pixels as on the Mac (PC 2 opens 1625 by 1016 logical, 3250 by 2032 physical, sharp); the remembered frame is logical on both hosts (the unaware host read virtualised coordinates), kept as it is, with a WindowUnits marker from here; six scaled cases in opening_size_test.c (the test could not compile before the function existed); tools/windows/dpi-check.ps1 is the read-only PC-job check (awareness, rectangle, monitor DPI, logical size; -Expect PerMonitorV2 fails on the 0.3.22 host, which reads Unaware); host.cpp touched

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 19:28:34 +00:00
parent ecc8fb5443
commit 826b29f6a5
7 changed files with 117 additions and 3 deletions

View file

@ -45,7 +45,7 @@ echo [build] rc
rc.exe /nologo /i "%ART%" /fo build\host.res host.rc || (pause & exit /b 1)
echo [build] cl
cl.exe /nologo /O2 /MT /EHsc /W3 /std:c++17 /DUNICODE /D_UNICODE /I "build\webview2\build\native\include" /Fo"build\\" host.cpp build\host.res ^
/link /SUBSYSTEM:WINDOWS /OUT:"dist\Igneum Miner.exe" "build\webview2\build\native\x64\WebView2LoaderStatic.lib" ^
/link /SUBSYSTEM:WINDOWS /MANIFEST:NO /OUT:"dist\Igneum Miner.exe" "build\webview2\build\native\x64\WebView2LoaderStatic.lib" ^
user32.lib shell32.lib ole32.lib advapi32.lib gdi32.lib version.lib shlwapi.lib || (pause & exit /b 1)
echo [build] done: dist\Igneum Miner.exe

View file

@ -348,14 +348,35 @@ static void beginQuit() {
// (WindowW, WindowH) and used on the next start while it still fits the work area. The Mac window does the same
// through its frame autosave name (app/mac/IgneumMiner.swift).
static const wchar_t* kSizeKey = L"Software\\Igneum\\Miner";
// dpi-23 (7 October 2026): the host is per-monitor DPI aware (host.manifest, and the runtime call below for a Windows that
// ignores the manifest), so every rectangle it reads is PHYSICAL pixels; the window's monitor DPI (96 = 100 percent) turns
// them into the logical pixels the opening-size rule and the remembered frame use. GetDpiForMonitor is looked up at run
// time (Shcore, Windows 8.1 and later) so the build links nothing new; the fallback is the desktop's LOGPIXELSX.
typedef HRESULT (WINAPI *GetDpiForMonitorFn)(HMONITOR, int, UINT*, UINT*);
static int monitorDpi(HMONITOR mon) {
HMODULE sh = LoadLibraryW(L"Shcore.dll");
if (sh) { GetDpiForMonitorFn f = (GetDpiForMonitorFn)GetProcAddress(sh, "GetDpiForMonitor"); UINT x = 96, y = 96; if (f && mon && SUCCEEDED(f(mon, 0 /* MDT_EFFECTIVE_DPI */, &x, &y)) && x > 0) { FreeLibrary(sh); return (int)x; } FreeLibrary(sh); }
HDC dc = GetDC(nullptr); int dpi = dc ? GetDeviceCaps(dc, LOGPIXELSX) : 96; if (dc) ReleaseDC(nullptr, dc); return dpi > 0 ? dpi : 96;
}
static void enableDpiAwareness() {
// the manifest asks for PerMonitorV2; this is the runtime path for a Windows build that ignores it (older than 1703)
HMODULE u = GetModuleHandleW(L"user32.dll"); if (!u) return;
typedef BOOL (WINAPI *SetCtxFn)(HANDLE);
SetCtxFn setCtx = (SetCtxFn)GetProcAddress(u, "SetProcessDpiAwarenessContext");
if (setCtx && setCtx((HANDLE)-4 /* DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 */)) return;
typedef BOOL (WINAPI *SetAwareFn)(void);
SetAwareFn setAware = (SetAwareFn)GetProcAddress(u, "SetProcessDPIAware"); if (setAware) setAware();
}
static void saveWindowSize(HWND hwnd) {
if (IsIconic(hwnd) || IsZoomed(hwnd)) return;
RECT r; if (!GetWindowRect(hwnd, &r)) return;
DWORD w = (DWORD)(r.right - r.left), h = (DWORD)(r.bottom - r.top);
int dpi = monitorDpi(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST));
DWORD w = (DWORD)igneum_scale_to_logical(r.right - r.left, dpi), h = (DWORD)igneum_scale_to_logical(r.bottom - r.top, dpi), units = 1;
if (w < 900 || h < 600 || w > (DWORD)(h * 1.6)) return;
HKEY k; if (RegCreateKeyExW(HKEY_CURRENT_USER, kSizeKey, 0, nullptr, 0, KEY_SET_VALUE, nullptr, &k, nullptr) != ERROR_SUCCESS) return;
RegSetValueExW(k, L"WindowW", 0, REG_DWORD, (const BYTE*)&w, sizeof(w));
RegSetValueExW(k, L"WindowH", 0, REG_DWORD, (const BYTE*)&h, sizeof(h));
RegSetValueExW(k, L"WindowUnits", 0, REG_DWORD, (const BYTE*)&units, sizeof(units)); // 1 = logical pixels, written by a DPI-aware host
RegCloseKey(k);
}
static void openingSize(int& w, int& h, int& sx, int& sy) {
@ -370,7 +391,8 @@ static void openingSize(int& w, int& h, int& sx, int& sy) {
RegQueryValueExW(k, L"WindowH", nullptr, nullptr, (LPBYTE)&sh, &n);
RegCloseKey(k);
}
igneum_opening_size(ww, wh, (int)sw, (int)sh, &w, &h);
// physical work area and DPI in, physical window out; the rule itself runs in logical pixels (opening_size.h)
igneum_opening_size_scaled(ww, wh, monitorDpi(mon), (int)sw, (int)sh, &w, &h, nullptr, nullptr);
sx = work.left + (ww - w) / 2; sy = work.top + (wh - h) / 2;
}
@ -382,6 +404,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
case WM_EXITSIZEMOVE:
saveWindowSize(hwnd);
return 0;
case WM_DPICHANGED: { // the window moved to a monitor of another scale: take the rectangle Windows suggests, WebView2 re-renders at the new DPI
const RECT* r = (const RECT*)lp;
if (r) SetWindowPos(hwnd, nullptr, r->left, r->top, r->right - r->left, r->bottom - r->top, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_GETMINMAXINFO: // the dashboard lays out from 900 x 600 up (app/igneum-app/ui); the Mac window says the same
((MINMAXINFO*)lp)->ptMinTrackSize.x = 900; ((MINMAXINFO*)lp)->ptMinTrackSize.y = 600;
return 0;
@ -533,6 +560,7 @@ static bool handleCliFlags() {
int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, PWSTR, int) {
if (handleCliFlags()) return 0;
enableDpiAwareness();
HANDLE once = CreateMutexW(nullptr, TRUE, L"Local\\IgneumMinerWindow");
if (GetLastError() == ERROR_ALREADY_EXISTS) {
HWND other = FindWindowW(L"IgneumMinerWindow", nullptr);

19
app/windows/host.manifest Normal file
View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Igneum Miner.exe (the window host): per-monitor DPI awareness v2 (dpi-23, 7 October 2026). Without it Windows
stretched the whole window on a scaled panel (PC 2's 5120 by 2160 at 200 percent rendered blurry); with it WebView2
renders at the panel's native scale and the host reads physical pixels. Embedded by host.rc as RT_MANIFEST 1;
BUILD-APP.bat links with /MANIFEST:NO so the linker's default manifest does not collide. -->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="Igneum.Miner" version="1.0.0.0"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
</assembly>

View file

@ -6,6 +6,9 @@
1 ICON "igneum.ico"
// dpi-23: the application manifest (per-monitor DPI awareness v2); the linker runs with /MANIFEST:NO
1 24 "host.manifest"
1 VERSIONINFO
FILEVERSION IGNEUM_HOST_VERSION_RC
PRODUCTVERSION IGNEUM_HOST_VERSION_RC

View file

@ -23,4 +23,20 @@ static void igneum_opening_size(int workW, int workH, int savedW, int savedH, in
if (savedW >= 900 && savedH >= 600 && savedW <= workW && savedH <= workH && savedW <= (int)(savedH * 1.632)) { w = savedW; h = savedH; }
*outW = w; *outH = h;
}
/* dpi-23 (7 October 2026): the same rule on a per-monitor-aware host, where the work area and the frame come in PHYSICAL
pixels and the monitor's DPI is known (96 = 100 percent). The rule runs in logical pixels (physical x 96 / dpi), so the
1440 by 900 and 1920 caps mean CSS pixels as they do on the Mac, and the result goes back to physical for CreateWindow.
The remembered frame is logical on both the old host (a DPI-unaware process reads virtualised, logical coordinates) and
this one (it saves physical / scale), so no stored value is thrown away for its units; the WindowUnits marker records which
host wrote it. PC 2: 5120 by 2112 physical at 192 DPI reads as 2560 by 1056 logical, opens 1625 by 1016 logical, 3250 by
2032 physical, sharp. */
static int igneum_scale_to_logical(int px, int dpi) { return dpi > 0 ? (int)((long long)px * 96 / dpi) : px; }
static int igneum_scale_to_physical(int lg, int dpi) { return dpi > 0 ? (int)((long long)lg * dpi / 96) : lg; }
static void igneum_opening_size_scaled(int workWpx, int workHpx, int dpi, int savedWlogical, int savedHlogical, int* outWpx, int* outHpx, int* outWlogical, int* outHlogical) {
int lw = 0, lh = 0;
igneum_opening_size(igneum_scale_to_logical(workWpx, dpi), igneum_scale_to_logical(workHpx, dpi), savedWlogical, savedHlogical, &lw, &lh);
if (outWlogical) *outWlogical = lw;
if (outHlogical) *outHlogical = lh;
*outWpx = igneum_scale_to_physical(lw, dpi); *outHpx = igneum_scale_to_physical(lh, dpi);
}
#endif

View file

@ -6,6 +6,11 @@
#include <stdio.h>
#include "opening_size.h"
static int fails = 0;
static void checkS(const char* name, int ww, int wh, int dpi, int sw, int sh, int ew, int eh, int elw, int elh) {
int w = 0, h = 0, lw = 0, lh = 0; igneum_opening_size_scaled(ww, wh, dpi, sw, sh, &w, &h, &lw, &lh);
if (w != ew || h != eh || lw != elw || lh != elh) { printf("FAIL %s: work %dx%d @%d dpi saved %dx%d -> %dx%d px (%dx%d logical), wanted %dx%d px (%dx%d)\n", name, ww, wh, dpi, sw, sh, w, h, lw, lh, ew, eh, elw, elh); fails++; }
else printf("ok %s: %dx%d px, %dx%d logical\n", name, w, h, lw, lh);
}
static void check(const char* name, int ww, int wh, int sw, int sh, int ew, int eh) {
int w = 0, h = 0; igneum_opening_size(ww, wh, sw, sh, &w, &h);
if (w != ew || h != eh) { printf("FAIL %s: work %dx%d saved %dx%d -> %dx%d, wanted %dx%d\n", name, ww, wh, sw, sh, w, h, ew, eh); fails++; }
@ -26,5 +31,12 @@ int main(void) {
check("a remembered frame under the minimum is discarded", 1920, 1040, 800, 500, 1440, 900);
check("a small laptop: 80 percent wide, 16:10", 1280, 760, 0, 0, 1024, 640);
check("a tiny work area: the work area itself", 800, 560, 0, 0, 800, 560);
/* dpi-23: the per-monitor-aware host reads physical pixels and the monitor's DPI */
checkS("1080p at 100 percent: unchanged", 1920, 1040, 96, 0, 0, 1440, 900, 1440, 900);
checkS("PC 2: 5120 by 2112 physical at 200 percent (2560 by 1056 logical)", 5120, 2112, 192, 0, 0, 3250, 2032, 1625, 1016);
checkS("4K at 150 percent: 1920 by 1200 logical, 2880 by 1800 physical", 3840, 2120, 144, 0, 0, 2880, 1800, 1920, 1200);
checkS("a 1440p panel at 125 percent (2048 by 1120 logical)", 2560, 1400, 120, 0, 0, 2045, 1278, 1636, 1023);
checkS("PC 2's remembered 1329 by 825 (logical, from the unaware host) is kept and placed at 200 percent", 5120, 2112, 192, 1329, 825, 2658, 1650, 1329, 825);
checkS("a 250 percent laptop panel (1536 by 832 logical): 80 percent wide at 16:10", 3840, 2080, 240, 0, 0, 3067, 1917, 1227, 767);
printf(fails ? "%d FAILED\n" : "all ok\n", fails); return fails ? 1 : 0;
}

View file

@ -0,0 +1,36 @@
# dpi-check.ps1 (dpi-23, 7 October 2026): READ-ONLY. Reports the installed Igneum Miner window's DPI awareness, its rectangle in
# physical pixels, its monitor's DPI and scale, and the logical size that follows; the PC job runs it at 100, 150, 200 and 250
# percent after the user sets the scale (the script sets nothing). With -Expect PerMonitorV2 it exits 1 when the awareness
# differs (known-failed on the 0.3.22 host, which is Unaware, so Windows stretched its render). Touches nothing: no click, no
# message to the window, no setting; a job never quits or restarts the installed app.
param([string]$Expect = "")
Add-Type @"
using System; using System.Runtime.InteropServices;
public static class Dpi {
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left, Top, Right, Bottom; }
[DllImport("user32.dll")] public static extern IntPtr FindWindowW([MarshalAs(UnmanagedType.LPWStr)] string cls, [MarshalAs(UnmanagedType.LPWStr)] string title);
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r);
[DllImport("user32.dll")] public static extern IntPtr GetWindowDpiAwarenessContext(IntPtr h);
[DllImport("user32.dll")] public static extern int GetAwarenessFromDpiAwarenessContext(IntPtr ctx);
[DllImport("user32.dll")] public static extern bool AreDpiAwarenessContextsEqual(IntPtr a, IntPtr b);
[DllImport("user32.dll")] public static extern IntPtr MonitorFromWindow(IntPtr h, uint flags);
[DllImport("shcore.dll")] public static extern int GetDpiForMonitor(IntPtr mon, int type, out uint x, out uint y);
[DllImport("user32.dll")] public static extern bool SetProcessDpiAwarenessContext(IntPtr ctx);
}
"@
[void][Dpi]::SetProcessDpiAwarenessContext([IntPtr](-4)) # this script reads physical pixels itself
$h = [Dpi]::FindWindowW("IgneumMinerWindow", $null)
if ($h -eq [IntPtr]::Zero) { Write-Output "dpi-check: no Igneum Miner window (class IgneumMinerWindow) on this desktop"; exit 2 }
$ctx = [Dpi]::GetWindowDpiAwarenessContext($h)
$aw = [Dpi]::GetAwarenessFromDpiAwarenessContext($ctx) # 0 Unaware, 1 System, 2 PerMonitor
$v2 = [Dpi]::AreDpiAwarenessContextsEqual($ctx, [IntPtr](-4))
$word = if ($v2) { "PerMonitorV2" } elseif ($aw -eq 2) { "PerMonitor" } elseif ($aw -eq 1) { "System" } else { "Unaware" }
$r = New-Object Dpi+RECT; [void][Dpi]::GetWindowRect($h, [ref]$r)
$mon = [Dpi]::MonitorFromWindow($h, 2); $x = [uint32]96; $y = [uint32]96; [void][Dpi]::GetDpiForMonitor($mon, 0, [ref]$x, [ref]$y)
$w = $r.Right - $r.Left; $hh = $r.Bottom - $r.Top; $scale = [math]::Round($x / 96.0, 2)
$lw = [math]::Round($w * 96 / $x); $lh = [math]::Round($hh * 96 / $x)
Write-Output ("dpi-check: awareness {0}; window {1}x{2} physical at {3},{4}; monitor {5} dpi ({6} percent); logical {7}x{8}" -f $word, $w, $hh, $r.Left, $r.Top, $x, [int]($scale * 100), $lw, $lh)
$reg = Get-ItemProperty -Path "HKCU:\Software\Igneum\Miner" -ErrorAction SilentlyContinue
if ($reg) { Write-Output ("dpi-check: remembered WindowW {0} WindowH {1} WindowUnits {2}" -f $reg.WindowW, $reg.WindowH, $reg.WindowUnits) }
if ($Expect -ne "" -and $word -ne $Expect) { Write-Output ("dpi-check: FAIL, expected {0}" -f $Expect); exit 1 }
exit 0