Fix native panel resolution detection for smooth scaling - #86
Fix native panel resolution detection for smooth scaling#86dboleslawski wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
This is the right fix for the right bug. I've seen the 1024x768-as-native case and never traced it back to the mode list being the only source I had. DisplayPixelDimensions is the piece I was missing, and I checked the encoding against Apple's own overrides (DisplayVendorID-610/DisplayProductID-9220 carries 00000A00 00000640 for a 2560x1600 panel), so that part is exactly right.
Four things before I can merge it.
-
The 6720 cap in
smoothScaledLogicalSizeskills mirror mode on Apple Silicon.MirroredModeService.beyondCapStops(from #65, merged a few days ago) draws its stops from that same function and keeps only the widths between the enumerable HiDPI top, around 3360, and native. Capping the source list at 3360 leaves it nothing to keep: for a 5120x1440 panel that's 109 stops down to 0, so the slider loses everything above 3360 andPresetServicecan no longer restore a preset captured while mirrored. The cap itself is a good idea and I want to keep it. It's a no-op at or below 3360 logical width and a real improvement on 4K, where it trims about 30 modes that could never enumerate. It just needs to be a parameter, because the beyond-cap sizes are rendered on a virtual display where the cap doesn't apply, which is the whole reason that path exists. -
On Apple Silicon the early break in
detectcan never fire. It needs both an EDID hit and a registry hit, but DCP doesn't publish the raw bytes, soedidstays nil and every detection walks the whole tree. On my M4 Max on 26.5.1 that's 2843 entries and 540 ms for a successful read of my AOC, andloadDetailswaits on it before starting the mode list, once per display on every connect. Could you match on the display nodes instead (IODisplayConnect, or the DCP AV service), relax the break to "found what this platform can give", and let the mode-list task run in parallel? TheDisplayAttributeslevel you read theNativeFormatpair from is correct, that matched exactly here. Also worth knowing: through a DisplayLink hub there's no EDID node in the registry at all, so the display-modes fallback is load-bearing there rather than vestigial. -
The manual override doesn't take effect until the display reconnects. Its real job is feeding
nativeAspectintoDisplayMode.availableModes, which decides which hidden CGS HiDPI modes get merged in, butsetPanelResolutionOverrideonly clears the view caches. Someone corrects a wrong panel size and the modes they were trying to unlock still aren't there. It needs aloadDetails()orHiDPIService.refreshModes(for:)after the write. -
In
inspect,edidResolution = EDIDParser.preferredResolution(from: data)assigns even when the parse returns nil, so a second identity-matching entry with an unreadable EDID wipes a good result. Assign only on success.
Small one: the README and docs/native-resolution.md both say "Resolution > Panel resolution", but the row is labelled "Native resolution".
Separate ask: could you pull the DisplayProfileController preset caching into its own PR? It's a real crash fix and unrelated to the title, and I'd rather land it now than have it wait on the rest of this.
Three things I'm not asking you to change, just so you know I looked at them. Taking the largest EDID timing across the base DTD and the CTA native entries inverts the spec's priority, which is clearly deliberate and handles the 1080p-base/4K-native TV, but it does mean a 1080p panel whose HDMI receiver flags a 4K SVD as native reads as 4K, and EDID now outranks the mode list. autoEnableHiDPIIfNeeded inherits that, so a 4K panel on a link that only carries 1080p would clear the 2K+ gate and prompt for admin on connect. And resolvedPanelResolution rebuilds an array over availableModes on every read of nativeResolution, which gets called from view bodies, so it's worth memoizing alongside availableModes at some point.
One style note: VirtualDisplayView.customField already has a panel-styled numeric field with digit filtering. The override editor's .roundedBorder fields and .borderedProminent Save button read like a different app inside the panel.
DisplayPresetView computed its preset list and the active preset inside body, through MonitorPanel KVC reads. Those reads can pump the main run loop, and a screen-parameter notification delivered from inside that nested loop sets published state mid-update, which trips AttributeGraph's precondition and aborts the app. DisplayProfileController.reload() now takes one snapshot of the presets and the active index from its .task, and the view renders from that cache and updates it after a successful switch. Split out of #86.
924bf23 to
df02f3d
Compare
|
Thanks — I’ve addressed all of the requested changes:
Also addressed the smaller points:
I left the three explicitly non-requested observations unchanged: the deliberate largest-native-timing EDID/CTA selection, its Because applying the override exercises the existing soft-reconnect and mirror teardown paths, I also hardened those paths against display-ID reassignment, finite WindowServer timeouts, late virtual-display registration, and uncertain display enumeration. The cleanup/recovery paths were independently audited after the changes. Validation is green: SwiftLint, the full test suite, localization-key validation, an Intel ( |

Summary
DisplayPixelDimensionsin the macOS display override so smooth scaling can activate after display reinitialization without requiring a reboot.Testing
make check