diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 40d4795cc..f1f4f0f7e 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -403,6 +403,29 @@ extension Ghostty { } override func viewDidChangeBackingProperties() { + super.viewDidChangeBackingProperties() + + // The Core Animation compositing engine uses the layer's contentsScale property + // to determine whether to scale its contents during compositing. When the window + // moves between a high DPI display and a low DPI display, or the user modifies + // the DPI scaling for a display in the system settings, this can result in the + // layer being scaled inappropriately. Since we handle the adjustment of scale + // and resolution ourselves below, we update the layer's contentsScale property + // to match the window's backingScaleFactor, so as to ensure it is not scaled by + // the compositor. + // + // Ref: High Resolution Guidelines for OS X + // https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW27 + if let window = window { + CATransaction.begin() + // Disable the implicit transition animation that Core Animation applies to + // property changes. Otherwise it will apply a scale animation to the layer + // contents which looks pretty janky. + CATransaction.setDisableActions(true) + layer?.contentsScale = window.backingScaleFactor + CATransaction.commit() + } + guard let surface = self.surface else { return } // Detect our X/Y scale factor so we can update our surface