From 3dc4200f668553b19c57bb2406d03b15b454181a Mon Sep 17 00:00:00 2001 From: Matt Edmondson Date: Thu, 16 Jul 2026 19:32:26 +1000 Subject: [PATCH 1/2] [patch] Add native macOS DPI detection path Modern macOS has no X11, but GetActualScaleFactor routed macOS into the X11 branch because XDG_SESSION_TYPE is null and matched `null or "x11"`. That P/Invoked libX11.so.6, which does not exist on macOS, throwing DllNotFoundException. Add a macOS branch that reads the main display backing scale via CoreGraphics (CGDisplayModeGetPixelWidth / CGDisplayModeGetWidth), giving 1.0 on standard displays and 2.0 on Retina. Falls back to the standard DPI scale when the display mode is unavailable or CoreGraphics cannot be loaded (for example, a headless host). --- ImGui.App/ForceDpiAware.cs | 55 ++++++++++++++++++++++++++++++++++++++ ImGui.App/NativeMethods.cs | 43 +++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/ImGui.App/ForceDpiAware.cs b/ImGui.App/ForceDpiAware.cs index ed151702..ee84f675 100644 --- a/ImGui.App/ForceDpiAware.cs +++ b/ImGui.App/ForceDpiAware.cs @@ -82,6 +82,12 @@ public static double GetActualScaleFactor() return GdiPlusHelper.GetDpiX(IntPtr.Zero); } + // Modern macOS has no X11; interrogate the display's backing scale via CoreGraphics. + if (OperatingSystem.IsMacOS()) + { + return GetMacOSDpiScale(); + } + string? xdgSessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower(); // X11 (and unspecified session type) uses X11 detection; everything else falls back to Wayland. @@ -129,6 +135,55 @@ private static double GetX11DpiScale() return userDpiScale; } + /// + /// Gets the DPI scale factor on macOS from the main display's backing scale factor. + /// + /// + /// The actual scale factor: on a standard display, + /// twice that on a Retina display. Falls back to when the + /// display mode cannot be read or CoreGraphics is unavailable (for example, a headless host). + /// + private static double GetMacOSDpiScale() + { + try + { + uint displayId = NativeMethods.CGMainDisplayID(); + nint mode = NativeMethods.CGDisplayCopyDisplayMode(displayId); + if (mode == IntPtr.Zero) + { + return StandardDpiScale; + } + + try + { + // The pixel-width to point-width ratio is the display's backing scale factor + // (1.0 on a standard display, 2.0 on Retina). + nuint pixelWidth = NativeMethods.CGDisplayModeGetPixelWidth(mode); + nuint pointWidth = NativeMethods.CGDisplayModeGetWidth(mode); + if (pointWidth == 0) + { + return StandardDpiScale; + } + + double scale = (double)pixelWidth / pointWidth; + return scale * StandardDpiScale; + } + finally + { + // CGDisplayCopyDisplayMode follows the Core Foundation "Copy" ownership rule. + NativeMethods.CGDisplayModeRelease(mode); + } + } + catch (DllNotFoundException) + { + return StandardDpiScale; + } + catch (EntryPointNotFoundException) + { + return StandardDpiScale; + } + } + /// /// Gets the window scale factor based on the actual scale factor and standard DPI scale. /// diff --git a/ImGui.App/NativeMethods.cs b/ImGui.App/NativeMethods.cs index 0e274e5f..2b1868dc 100644 --- a/ImGui.App/NativeMethods.cs +++ b/ImGui.App/NativeMethods.cs @@ -274,4 +274,47 @@ internal struct StartupOutput [LibraryImport(GDILibraryName)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] internal static partial int GdipGetDpiX(IntPtr graphics, out float dpi); + + // CoreGraphics is loaded by its full framework path; macOS frameworks are not on the + // default dylib search path, so a bare "CoreGraphics" name would not resolve. No + // DefaultDllImportSearchPaths here: that attribute controls the Windows loader only. + private const string CoreGraphicsLibraryName = "/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics"; + + /// + /// Returns the display ID of the main display. + /// + /// The CGDirectDisplayID of the main display. + [LibraryImport(CoreGraphicsLibraryName)] + internal static partial uint CGMainDisplayID(); + + /// + /// Returns a copy of the current display mode for the given display. + /// + /// The display to query. + /// A CGDisplayModeRef the caller owns and must release with , or on failure. + [LibraryImport(CoreGraphicsLibraryName)] + internal static partial nint CGDisplayCopyDisplayMode(uint display); + + /// + /// Returns the width, in physical pixels, of the specified display mode. + /// + /// The display mode to query. + /// The pixel width of the display mode. + [LibraryImport(CoreGraphicsLibraryName)] + internal static partial nuint CGDisplayModeGetPixelWidth(nint mode); + + /// + /// Returns the width, in points, of the specified display mode. + /// + /// The display mode to query. + /// The point width of the display mode. + [LibraryImport(CoreGraphicsLibraryName)] + internal static partial nuint CGDisplayModeGetWidth(nint mode); + + /// + /// Releases a display mode obtained from . + /// + /// The display mode to release. + [LibraryImport(CoreGraphicsLibraryName)] + internal static partial void CGDisplayModeRelease(nint mode); } From 01ffba1e8e9c8479ab4826dd647e92ec923ceddd Mon Sep 17 00:00:00 2001 From: Matt Edmondson Date: Fri, 24 Jul 2026 15:13:47 +1000 Subject: [PATCH 2/2] test(app): cover macOS DPI scale math, exclude native orchestration The macOS DPI branch was all native CoreGraphics interop that only runs on a Mac with a display, so it registered 0% new-code coverage on the Windows coverage runner and tripped the SonarCloud coverage gate. Extract the pure pixel-to-point scale arithmetic into an internal MacOSBackingScaleToDpi helper and unit-test it (standard, Retina, fractional, zero-width fallback). Mark the thin native wrapper GetMacOSDpiScale with [ExcludeFromCodeCoverage] and a justification, matching the repo's treatment of other C ABI boundary code. Microsoft Code Coverage honors the attribute, removing the unreachable lines from the denominator. --- ImGui.App/ForceDpiAware.cs | 35 ++++++++++++++---- tests/ImGui.App.Tests/ForceDpiAwareTests.cs | 40 +++++++++++++++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/ImGui.App/ForceDpiAware.cs b/ImGui.App/ForceDpiAware.cs index ee84f675..fad6d3aa 100644 --- a/ImGui.App/ForceDpiAware.cs +++ b/ImGui.App/ForceDpiAware.cs @@ -143,6 +143,12 @@ private static double GetX11DpiScale() /// twice that on a Retina display. Falls back to when the /// display mode cannot be read or CoreGraphics is unavailable (for example, a headless host). /// + // This method is pure native orchestration over the CoreGraphics P/Invokes; every line only + // runs on a macOS host with a display, so it cannot be exercised by the coverage runner (which + // runs on Windows). The testable scale arithmetic lives in MacOSBackingScaleToDpi, which is + // covered by unit tests. Excluded from coverage for that reason, matching the repository's + // treatment of other native C ABI boundary code. + [ExcludeFromCodeCoverage(Justification = "Native CoreGraphics orchestration; only reachable on a macOS host with a display. Testable arithmetic is factored into MacOSBackingScaleToDpi.")] private static double GetMacOSDpiScale() { try @@ -160,13 +166,7 @@ private static double GetMacOSDpiScale() // (1.0 on a standard display, 2.0 on Retina). nuint pixelWidth = NativeMethods.CGDisplayModeGetPixelWidth(mode); nuint pointWidth = NativeMethods.CGDisplayModeGetWidth(mode); - if (pointWidth == 0) - { - return StandardDpiScale; - } - - double scale = (double)pixelWidth / pointWidth; - return scale * StandardDpiScale; + return MacOSBackingScaleToDpi(pixelWidth, pointWidth); } finally { @@ -184,6 +184,27 @@ private static double GetMacOSDpiScale() } } + /// + /// Converts a macOS display mode's pixel and point widths into an actual DPI scale factor. + /// + /// The display mode width in physical pixels. + /// The display mode width in points. + /// + /// scaled by the pixel-to-point ratio (the display's backing + /// scale factor: 1.0 on a standard display, 2.0 on Retina), or + /// when is zero. + /// + internal static double MacOSBackingScaleToDpi(nuint pixelWidth, nuint pointWidth) + { + if (pointWidth == 0) + { + return StandardDpiScale; + } + + double scale = (double)pixelWidth / pointWidth; + return scale * StandardDpiScale; + } + /// /// Gets the window scale factor based on the actual scale factor and standard DPI scale. /// diff --git a/tests/ImGui.App.Tests/ForceDpiAwareTests.cs b/tests/ImGui.App.Tests/ForceDpiAwareTests.cs index 9600c6fe..c2ac59de 100644 --- a/tests/ImGui.App.Tests/ForceDpiAwareTests.cs +++ b/tests/ImGui.App.Tests/ForceDpiAwareTests.cs @@ -29,4 +29,44 @@ public void GetActualScaleFactor_ReturnsValidValue() // Assert Assert.IsGreaterThan(0, actualScale, "Actual scale factor should be greater than 0"); } + + [TestMethod] + public void MacOSBackingScaleToDpi_StandardDisplay_ReturnsStandardDpi() + { + // Arrange: pixel width equals point width on a non-Retina display (1x backing scale). + double dpi = ForceDpiAware.MacOSBackingScaleToDpi(1920, 1920); + + // Assert + Assert.AreEqual(ForceDpiAware.StandardDpiScale, dpi, "A 1x display should map to the standard DPI scale"); + } + + [TestMethod] + public void MacOSBackingScaleToDpi_RetinaDisplay_ReturnsDoubleStandardDpi() + { + // Arrange: pixels are twice the points on a Retina display (2x backing scale). + double dpi = ForceDpiAware.MacOSBackingScaleToDpi(3840, 1920); + + // Assert + Assert.AreEqual(ForceDpiAware.StandardDpiScale * 2.0, dpi, "A 2x Retina display should map to twice the standard DPI scale"); + } + + [TestMethod] + public void MacOSBackingScaleToDpi_FractionalScale_ScalesProportionally() + { + // Arrange: a 1.5x backing scale (e.g. a scaled Retina mode). + double dpi = ForceDpiAware.MacOSBackingScaleToDpi(2880, 1920); + + // Assert + Assert.AreEqual(ForceDpiAware.StandardDpiScale * 1.5, dpi, "A 1.5x display should scale the standard DPI proportionally"); + } + + [TestMethod] + public void MacOSBackingScaleToDpi_ZeroPointWidth_FallsBackToStandardDpi() + { + // Arrange: a zero point width (unreadable mode) must not divide by zero. + double dpi = ForceDpiAware.MacOSBackingScaleToDpi(1920, 0); + + // Assert + Assert.AreEqual(ForceDpiAware.StandardDpiScale, dpi, "A zero point width should fall back to the standard DPI scale"); + } }