Merge dpi-23 826b29f6 into release-0.3.23 (per-monitor DPI awareness for the Windows host: the manifest, WM_DPICHANGED, the opening size in logical pixels)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
commit
b57201d0d7
7 changed files with 117 additions and 3 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
19
app/windows/host.manifest
Normal 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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
36
tools/windows/dpi-check.ps1
Normal file
36
tools/windows/dpi-check.ps1
Normal 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
|
||||
Loading…
Reference in a new issue