mirror of https://github.com/cirruslabs/tart.git
Merge 60bf256345 into 4ce8a115f7
This commit is contained in:
commit
607065b0bd
|
|
@ -14,7 +14,7 @@ struct Set: AsyncParsableCommand {
|
|||
@Option(help: "VM memory size in megabytes")
|
||||
var memory: UInt64?
|
||||
|
||||
@Option(help: "VM display resolution in a format of WIDTHxHEIGHT[pt|px]. For example, 1200x800, 1200x800pt or 1920x1080px. Units are treated as hints and default to \"pt\" (points) for macOS VMs and \"px\" (pixels) for Linux VMs when not specified.")
|
||||
@Option(help: "VM display resolution in a format of WIDTHxHEIGHT[pt|px][@PPI]. For example, 1200x800, 1200x800pt, 1920x1080px or 3200x1800px@220. Units are treated as hints and default to \"pt\" (points) for macOS VMs and \"px\" (pixels) for Linux VMs when not specified. The optional @PPI (pixels-per-inch) hint applies to pixel-unit displays and, at a Retina-class value such as 220, makes a macOS guest expose a HiDPI (2x) mode regardless of the host display; it defaults to 72 (non-Retina) when omitted.")
|
||||
var display: VMDisplayConfig?
|
||||
|
||||
@Flag(inversion: .prefixedNo, help: ArgumentHelp("Whether to automatically reconfigure the VM's display to fit the window"))
|
||||
|
|
@ -65,6 +65,7 @@ struct Set: AsyncParsableCommand {
|
|||
vmConfig.display.height = display.height
|
||||
}
|
||||
vmConfig.display.unit = display.unit
|
||||
vmConfig.display.ppi = display.ppi
|
||||
}
|
||||
|
||||
vmConfig.displayRefit = displayRefit
|
||||
|
|
@ -99,6 +100,16 @@ extension VMDisplayConfig: ExpressibleByArgument {
|
|||
public init(argument: String) {
|
||||
var argument = argument
|
||||
var unit: Unit? = nil
|
||||
var ppi: Int? = nil
|
||||
|
||||
// Optional "@PPI" pixels-per-inch hint, e.g. "3200x1800px@220". Parsed
|
||||
// before the unit suffix since it always trails the whole spec. Only a
|
||||
// positive value is kept; zero/negative (or non-numeric) is ignored so the
|
||||
// display falls back to the 72 default rather than a broken configuration.
|
||||
if let atIndex = argument.lastIndex(of: "@") {
|
||||
ppi = Int(argument[argument.index(after: atIndex)...]).flatMap { $0 > 0 ? $0 : nil }
|
||||
argument = String(argument[..<atIndex])
|
||||
}
|
||||
|
||||
if argument.hasSuffix(Unit.pixel.rawValue) {
|
||||
argument = String(argument.dropLast(Unit.pixel.rawValue.count))
|
||||
|
|
@ -115,6 +126,7 @@ extension VMDisplayConfig: ExpressibleByArgument {
|
|||
width: parts[safe: 0] ?? 0,
|
||||
height: parts[safe: 1] ?? 0,
|
||||
unit: unit,
|
||||
ppi: ppi,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,9 +95,11 @@ struct UnsupportedHostOSError: Error, CustomStringConvertible {
|
|||
VZMacGraphicsDisplayConfiguration(
|
||||
widthInPixels: vmConfig.display.width,
|
||||
heightInPixels: vmConfig.display.height,
|
||||
// A reasonable guess according to Apple's documentation[1]
|
||||
// Defaults to 72 — a reasonable guess according to Apple's
|
||||
// documentation[1] — unless a --display "@PPI" hint asks for a
|
||||
// Retina-class density (which yields a HiDPI guest mode).
|
||||
// [1]: https://developer.apple.com/documentation/coregraphics/1456599-cgdisplayscreensize
|
||||
pixelsPerInch: 72
|
||||
pixelsPerInch: vmConfig.display.effectivePixelsPerInch
|
||||
)
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -41,15 +41,32 @@ struct VMDisplayConfig: Codable, Equatable {
|
|||
var width: Int = 1024
|
||||
var height: Int = 768
|
||||
var unit: Unit?
|
||||
var ppi: Int?
|
||||
|
||||
// Pixels-per-inch handed to the guest's pixel-unit framebuffer. Defaults to
|
||||
// 72 (non-Retina) when no positive hint is given, matching
|
||||
// Virtualization.framework's historical assumption; a Retina-class value
|
||||
// (e.g. 220) makes the guest expose a HiDPI (scaling:on) mode regardless of
|
||||
// the host display. A non-positive stored value (e.g. from a hand-edited
|
||||
// config) is treated as unset so it never reaches the display configuration.
|
||||
var effectivePixelsPerInch: Int {
|
||||
if let ppi, ppi > 0 {
|
||||
return ppi
|
||||
}
|
||||
return 72
|
||||
}
|
||||
}
|
||||
|
||||
extension VMDisplayConfig: CustomStringConvertible {
|
||||
var description: String {
|
||||
var result = "\(width)x\(height)"
|
||||
if let unit {
|
||||
"\(width)x\(height)\(unit.rawValue)"
|
||||
} else {
|
||||
"\(width)x\(height)"
|
||||
result += unit.rawValue
|
||||
}
|
||||
if let ppi {
|
||||
result += "@\(ppi)"
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,4 +15,61 @@ final class VMConfigTests: XCTestCase {
|
|||
vmDisplayConfig = VMDisplayConfig.init(argument: "1234x5678px")
|
||||
XCTAssertEqual(VMDisplayConfig(width: 1234, height: 5678, unit: .pixel), vmDisplayConfig)
|
||||
}
|
||||
|
||||
func testEffectivePixelsPerInchUsesConfiguredValue() throws {
|
||||
let config = VMDisplayConfig(width: 3200, height: 1800, unit: .pixel, ppi: 220)
|
||||
XCTAssertEqual(220, config.effectivePixelsPerInch)
|
||||
}
|
||||
|
||||
func testEffectivePixelsPerInchDefaultsTo72WhenUnset() throws {
|
||||
// Upstream behavior: a pixel display with no PPI hint stays non-Retina (72 PPI).
|
||||
let config = VMDisplayConfig(width: 1234, height: 5678, unit: .pixel, ppi: nil)
|
||||
XCTAssertEqual(72, config.effectivePixelsPerInch)
|
||||
}
|
||||
|
||||
func testParsesPixelsPerInchSuffix() throws {
|
||||
let config = VMDisplayConfig(argument: "3200x1800px@220")
|
||||
XCTAssertEqual(VMDisplayConfig(width: 3200, height: 1800, unit: .pixel, ppi: 220), config)
|
||||
}
|
||||
|
||||
func testWithoutSuffixHasNilPixelsPerInch() throws {
|
||||
// Backward compatibility: existing "WIDTHxHEIGHT[pt|px]" strings carry no PPI.
|
||||
XCTAssertEqual(
|
||||
VMDisplayConfig(width: 1234, height: 5678, unit: .pixel, ppi: nil),
|
||||
VMDisplayConfig(argument: "1234x5678px"))
|
||||
}
|
||||
|
||||
func testDescriptionRoundTripsPixelsPerInch() throws {
|
||||
let config = VMDisplayConfig(width: 3200, height: 1800, unit: .pixel, ppi: 220)
|
||||
XCTAssertEqual("3200x1800px@220", config.description)
|
||||
XCTAssertEqual(config, VMDisplayConfig(argument: config.description))
|
||||
}
|
||||
|
||||
func testParsesPixelsPerInchWithoutExplicitUnit() throws {
|
||||
let config = VMDisplayConfig(argument: "3200x1800@220")
|
||||
XCTAssertEqual(VMDisplayConfig(width: 3200, height: 1800, unit: nil, ppi: 220), config)
|
||||
}
|
||||
|
||||
func testMalformedPixelsPerInchDegradesToNil() throws {
|
||||
// A non-numeric PPI is ignored (falls back to the 72 default) rather than
|
||||
// failing the parse — consistent with the parser's lenient dimensions.
|
||||
let config = VMDisplayConfig(argument: "3200x1800px@notanumber")
|
||||
XCTAssertEqual(VMDisplayConfig(width: 3200, height: 1800, unit: .pixel, ppi: nil), config)
|
||||
XCTAssertEqual(72, config.effectivePixelsPerInch)
|
||||
}
|
||||
|
||||
func testRejectsNonPositivePixelsPerInchWhenParsing() throws {
|
||||
// A zero or negative PPI is meaningless and must not persist — it would
|
||||
// otherwise reach VZMacGraphicsDisplayConfiguration and fail to build the
|
||||
// display instead of falling back to the 72 default.
|
||||
XCTAssertNil(VMDisplayConfig(argument: "3200x1800px@0").ppi)
|
||||
XCTAssertNil(VMDisplayConfig(argument: "3200x1800px@-5").ppi)
|
||||
}
|
||||
|
||||
func testEffectivePixelsPerInchIgnoresNonPositiveStoredValue() throws {
|
||||
// Defense for a hand-edited config.json: a stored value <= 0 falls back to
|
||||
// 72 rather than being handed to VZMacGraphicsDisplayConfiguration.
|
||||
XCTAssertEqual(72, VMDisplayConfig(width: 100, height: 100, unit: .pixel, ppi: 0).effectivePixelsPerInch)
|
||||
XCTAssertEqual(72, VMDisplayConfig(width: 100, height: 100, unit: .pixel, ppi: -5).effectivePixelsPerInch)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue