fix(ui): prevent crash when window size is zero#7656
fix(ui): prevent crash when window size is zero#7656ty-yqs wants to merge 1 commit intoutmapp:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a macOS NSWindow subclass to sanitize invalid VM display window frame sizes (notably {0,0,0,0} via AppleScript) to prevent UI crashes that can terminate running VMs.
Changes:
- Introduces
VMDisplayWindow(NSWindowsubclass) that clamps window frames to a minimum size duringsetFramecalls and logs warnings for invalid dimensions. - Updates
VMDisplayWindow.xibto useVMDisplayWindowas the window’s custom class so the sanitization applies at runtime.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| Platform/macOS/Display/VMDisplayWindow.swift | Adds NSWindow subclass that clamps frame size and logs invalid resize attempts. |
| Platform/macOS/Display/Base.lproj/VMDisplayWindow.xib | Switches the window in the VM display nib from NSWindow to VMDisplayWindow. |
| override var minSize: NSSize { | ||
| get { | ||
| return NSSize(width: kMinWindowWidth, height: kMinWindowHeight) | ||
| } | ||
| set { | ||
| super.minSize = newValue | ||
| } | ||
| } |
There was a problem hiding this comment.
Overriding minSize to always return the hard-coded 400x300 breaks existing callers that intentionally set window.minSize at runtime (e.g. VMDisplayAppleDisplayWindowController.updateWindowFrame() sets window.minSize based on content/dynamic resolution). AppKit will consult this overridden getter, so the effective minimum size becomes fixed and those code paths can no longer raise the minimum when needed. Consider removing this override and instead (a) set super.minSize to the base minimum in awakeFromNib/init, and (b) when sanitizing frames, clamp to max(super.minSize, baseMinimum) so higher minimums set by controllers are still respected.
| private let kMinWindowWidth: CGFloat = 400 | ||
| private let kMinWindowHeight: CGFloat = 300 | ||
|
|
||
| private func sanitizeFrame(_ frameRect: NSRect) -> NSRect { | ||
| var frame = frameRect | ||
|
|
||
| // Check for invalid or too small dimensions | ||
| if frame.size.width < kMinWindowWidth { | ||
| // Log warning only if it's significantly invalid (e.g. 0 or negative) | ||
| if frame.size.width <= 0 { | ||
| logger.warning("Attempted to set invalid window width: \(frame.size.width). Clamping to \(kMinWindowWidth).") | ||
| } | ||
| frame.size.width = kMinWindowWidth | ||
| } | ||
|
|
||
| if frame.size.height < kMinWindowHeight { | ||
| if frame.size.height <= 0 { | ||
| logger.warning("Attempted to set invalid window height: \(frame.size.height). Clamping to \(kMinWindowHeight).") | ||
| } | ||
| frame.size.height = kMinWindowHeight | ||
| } | ||
|
|
There was a problem hiding this comment.
sanitizeFrame(_:) clamps against the hard-coded constants rather than the window’s current minSize. This can conflict with runtime minimum sizes that other parts of the app set on the window (for example to enforce an aspect ratio or content-driven minimum). Consider clamping against max(baseMinimum, super.minSize) so the crash fix still applies while preserving larger minimum sizes configured elsewhere.
| // Check for invalid or too small dimensions | ||
| if frame.size.width < kMinWindowWidth { | ||
| // Log warning only if it's significantly invalid (e.g. 0 or negative) | ||
| if frame.size.width <= 0 { | ||
| logger.warning("Attempted to set invalid window width: \(frame.size.width). Clamping to \(kMinWindowWidth).") | ||
| } | ||
| frame.size.width = kMinWindowWidth | ||
| } | ||
|
|
||
| if frame.size.height < kMinWindowHeight { | ||
| if frame.size.height <= 0 { | ||
| logger.warning("Attempted to set invalid window height: \(frame.size.height). Clamping to \(kMinWindowHeight).") |
There was a problem hiding this comment.
sanitizeFrame(_:) doesn’t handle NaN/±infinity dimensions: comparisons like width < kMinWindowWidth are false for NaN, so an invalid incoming frame can bypass clamping and still propagate into AppKit. If the goal is to prevent crashes from invalid geometry, treat non-finite sizes as invalid and clamp them to the minimum as well (e.g. check isFinite before comparing).
| // Check for invalid or too small dimensions | |
| if frame.size.width < kMinWindowWidth { | |
| // Log warning only if it's significantly invalid (e.g. 0 or negative) | |
| if frame.size.width <= 0 { | |
| logger.warning("Attempted to set invalid window width: \(frame.size.width). Clamping to \(kMinWindowWidth).") | |
| } | |
| frame.size.width = kMinWindowWidth | |
| } | |
| if frame.size.height < kMinWindowHeight { | |
| if frame.size.height <= 0 { | |
| logger.warning("Attempted to set invalid window height: \(frame.size.height). Clamping to \(kMinWindowHeight).") | |
| // Check for invalid or too small dimensions, including non-finite values | |
| let width = frame.size.width | |
| if !width.isFinite || width < kMinWindowWidth { | |
| // Log warning only if it's significantly invalid (e.g. non-finite, 0 or negative) | |
| if !width.isFinite || width <= 0 { | |
| logger.warning("Attempted to set invalid window width: \(width). Clamping to \(kMinWindowWidth).") | |
| } | |
| frame.size.width = kMinWindowWidth | |
| } | |
| let height = frame.size.height | |
| if !height.isFinite || height < kMinWindowHeight { | |
| if !height.isFinite || height <= 0 { | |
| logger.warning("Attempted to set invalid window height: \(height). Clamping to \(kMinWindowHeight).") |
Description
This PR addresses a critical issue reported in #7650 where setting a VM window's bounds to
{0, 0, 0, 0}(e.g., via AppleScript or automation) causes the application to crash. This crash would forcefully terminate all running virtual machines, potentially leading to data loss.The root cause was that the standard
NSWindowallows setting a zero frame size, which subsequently causes failures in the layout or rendering pipeline (e.g., coordinate calculations resulting in NaN or division by zero).Changes
Platform/macOS/Display/VMDisplayWindow.swiftNSWindowsubclass namedVMDisplayWindow.sanitizeFrame(_:)logic to enforce a minimum safe window size (400x300) and prevent invalid dimensions (width/height <= 0).setFramemethods to intercept all resize attempts and apply sanitized bounds.Platform/macOS/Display/Base.lproj/VMDisplayWindow.xibNSWindowto the customVMDisplayWindowto activate the safe resizing logic.Testing Instructions
"vmname"with your VM's window title):"Attempted to set invalid window width...".Related Issue
Fixes #7650