macOS 27 lets the host manage Secure Boot state in the EFI variable
store. Add a persisted flag and a toggle in the Boot settings so generic
UEFI guests on the Apple backend can opt in. The state is reconciled when
the configuration is saved, using the default platform key and the
default Microsoft signatures so that mainstream Linux distributions boot
unmodified. The store is only written when its state differs from the
configuration, and a virtual machine with the option off never fails to
save because of it. The option defaults to off because guests without a
Microsoft-signed bootloader would otherwise stop booting.
Assisted-by: Claude:claude-fable-5-1
macOS 27 can create the guest's user account, turn on automatic login and
Remote Login without going through Setup Assistant, which lets CI and other
automation get a ready-to-use macOS VM. The guest only reads these options
on the first boot after macOS is installed and they include a password, so
they are passed as a parameter to a single start and are never saved to the
configuration or the registry.
This is only exposed to AppleScript (`start ... provisioning {...}`) and
utmctl (`start --provision-*`) because most users will never need it. utmctl
can read the password from standard input to keep it out of the process
list. Requests that cannot be honoured (not a macOS guest, host older than
macOS 27, install still pending, saved state, recovery mode, invalid account)
fail before the VM starts so the first boot is not used up.
UTMScripting.swift has only the regenerated `start` declaration. The rest of
the current bridge-gen.sh output is unrelated to this change.
Assisted-by: Claude:claude-fable-5-1
A full screen VM on a Mac with a camera housing loses the strip beside
it, which the guest could otherwise use. Two things keep a native full
screen window out of that strip: AppKit insets the full screen frame and
the window server covers the strip with an opaque menu bar. There is no
public way around either, and a borderless window is not an option
because macOS would no longer treat the VM as a full screen app.
Add an opt-in preference (macOS 27+) and a window subclass that returns
the whole screen as the full screen frame and makes the menu bar of its
own full screen space transparent while it is not revealed. When the menu
bar is revealed it looks and behaves as it always did. It works within
the App Sandbox.
All of it relies on private AppKit and SkyLight symbols. None of them are
used or hooked until the option is enabled and a window enters full
screen, so the window is unaffected by changes to them otherwise. They
are looked up at runtime by name and checked against the signatures we
expect, so no private symbol is linked or compiled into the binary and
if any are missing or different the window behaves like any other.
The window server reveals the menu bar as soon as the pointer is over the
strip, which would make that area of the guest display unusable. It is
instead only revealed when the pointer reaches the top edge of the
screen: until then the space uses presentation options that hide the menu
bar, which also hides the Dock. AppKit applies the options of the space
again when it changes spaces, so they are kept up to date.
The window server only lets a window cover the strip if it does so from
the moment it enters the full screen space, which shapes the rest:
- Every frame query in the transition is answered with the whole screen,
and later requests to resize the window are ignored, because asking
for any frame again, even the current one, moves it out of the strip.
Our window controllers do so to fit the guest display, and AppKit does
when the space becomes active again.
- Once the strip has been left, such as when tiled in Split View, it is
not asked for again until full screen is next entered.
- The QEMU window is no longer resized to fit a display the guest just
changed while the transition to full screen is still running. The flag
the controller kept for that is only set once the transition is over,
so the window ended up taller than the screen and lost the strip.
- AppKit parks the auto hidden toolbar behind the menu bar, or over our
content while the menu bar is hidden, so it is hidden along with it.
The window stands down when tiled, on a screen without a camera housing,
and when the menu bar is set to always be visible in full screen.
Terminal windows do not opt in.
Resolves#6183
Assisted-by: Claude:claude-fable-5-1
Assisted-by: Claude:claude-opus-5
"Run without saving changes" has only been available for QEMU, where it
maps to `-snapshot`. Apple Virtualization has no such option, but
DiskImageKit on macOS 27 can stack a copy-on-write layer on top of an
image and Virtualization accepts the stacked image as a storage
attachment.
When started with the disposable option, the Apple backend now creates a
temporary directory in the sandbox and gives every writable drive an
ephemeral ASIF overlay there. The base image is opened read-only and is
never touched. The EFI variable store and the macOS auxiliary storage are
cloned into the same directory so firmware settings are not persisted
either. The directory is deleted when the guest stops or when start
fails, any leftover from a previous start is discarded before the next
start, and directories orphaned by a crash are swept at app launch.
Removable drives attached at start or hot-plugged during the run are
covered as well.
A disposable run boots fresh and leaves any saved suspend state and the
modification date alone, and reports the save/restore path as
unsupported so closing the window offers to kill the VM instead of
suspending it, matching the QEMU behaviour. The stop paths that delete a
saved suspend state skip that while running disposably.
The context menu, the Start intent and the menu bar extra now check the
backend capability instead of assuming QEMU, and the Apple display window
shows the same "Disposable Mode" subtitle as QEMU.
Assisted-by: Claude:claude-fable-5-1
ASIF drives for Apple Virtualization are created, inspected and resized
through the private DiskImages2 framework, loaded by path and called via
NSInvocation. macOS 27 ships DiskImageKit, a public Swift-only framework
that covers the same operations and is what Virtualization.framework now
consumes directly.
Add UTMAppleDiskImage, a Swift wrapper that uses DiskImageKit on macOS 27
and later and falls back to the existing UTMASIFImage helper on macOS 13
through 26. The legacy path is confined to one private extension so it
can be deleted together with UTMASIFImage once the minimum host is
macOS 27. DiskImageKit is weak linked because the deployment target is
below macOS 27.
Callers switch to the wrapper; the resize path no longer force-unwraps
the helper.
Assisted-by: Claude:claude-fable-5-1
createAppleVirtualMachine hardcodes the guest OS:
config.system.boot = try UTMAppleConfigurationBoot(for: .linux)
Every other field in the record is already honoured via
updateConfiguration(from:). The OS is the only thing a script cannot ask
for, and UTMAppleConfigurationBoot.init(for:) already accepts one.
This reads `os` from the record (as an AEKeyword, the same as `backend`;
sdef enumerations do not arrive as strings), defaulting to .linux so
existing scripts are unaffected. When the OS is macOS it requires and sets
`macRecoveryIpsw`, since without it the VM has nothing to restore from.
It also derives macPlatform from the caller's IPSW. fillVZConfiguration
throws platformUnsupported for a macOS guest with no platform, so setting
the OS and IPSW alone produces a VM that is created and then fails to
start with "This virtual machine cannot run on the current host machine."
The hardware model comes from the caller's own image via
VZMacOSRestoreImage.image(from:) rather than fetchLatestSupported: the
caller named a specific IPSW and the model must match it. An IPSW this
Mac cannot run fails with the same error the wizard shows.
And it gives a macOS guest a display, audio, keyboard and pointer by
default. The scripted create clears `displays` (the headless-by-default
convention that suits a Linux guest with a serial console), and
fillVZConfiguration only builds a graphics device when `displays` is
non-empty. For macOS that produced a guest with no framebuffer: it booted
to 0% CPU and UTMData.run chose a headless session because there was
nothing to show. The defaults mirror VMWizardState for a new macOS VM
(1920x1200, audio, generic keyboard, trackpad on Ventura+). A caller may
still override `displays`, but the input and audio devices cannot be set
from a script, so they are always enabled for a macOS guest.
The sdef gains an `operating system` enumeration (linux / macOS) and a
`macRecoveryIpsw` file property. The property name matches the existing
macRecoveryIpswURL and the "IPSW Install Image" label in
VMConfigAppleBootView.
UTMScripting.swift: the UTMScriptingOperatingSystem enum matches the
output of bridge-gen.sh for this sdef change.
Verified against main on Xcode 26.6 / Apple Silicon: a macOS 26.6.2 VM
created from AppleScript with OperatingSystem=macOS, a HardwareModel,
MachineIdentifier, AuxiliaryStoragePath and a 1920x1200 Display in the
resulting config.plist, and booted to Setup Assistant in a UTM display
window.
Refs #6092
Assisted-by: Claude:claude-opus-5
A macOS install takes several minutes, well past the default two minute
Apple Event timeout. A plain `install` therefore failed with -1712 while
the installation kept running, and a script reacting to that error by
retrying or starting the VM would race the install.
`install` now returns as soon as the installation starts, and the new
`query install` verb reports whether it is still running and how far it
has progressed, or raises the error if it failed. The installation only
counts as finished once the VM has stopped by itself, so the VM can be
started right away.
Other verbs fail with a clear error while an installation started from
a script is in progress. This includes `start`, which would otherwise
open a window that asks to install macOS again. As in the GUI, `stop`
cancels the installation.
Assisted-by: Claude:claude-opus-5
VMDisplayAppleWindowController.windowDidLoad shows a confirmation sheet
before calling requestInstallVM, so a macOS install cannot be driven from
a script without someone clicking OK.
This does not remove the sheet. It guards a genuinely destructive action
(it erases the primary drive) and a GUI user who opens a VM that happens
to carry an IPSW should keep being asked. It is also structurally
unavailable to a script: showConfirmAlert calls
beginSheetModal(for: window!), so it needs a window, and the scripted
`start` verb calls data.run(...), which loads the window controller and
raises the sheet in the first place.
So this adds a separate `install` verb that goes straight to
installVM(with:) and never opens a window. The confirmation moves rather
than disappearing: asking for `install` is the confirmation, which is why
the sdef entry says DESTRUCTIVE in as many words. This matches the
CONTRIBUTING.md guidance about putting things most users do not need into
the scripting interface rather than the UI.
It also clears the IPSW on success, as the GUI does in
didCompleteInstallation. That clearing lives in the window controller's
delegate callback, and a scripted install has no window, so without it
the IPSW outlives the install and the next `start` that does open a
display window asks "Would you like to install macOS?" over a guest that
is already installed. Measured before the fix: the registry still held
the IPSW after a completed scripted install.
Two guards, because installVM returns silently when the VM is not
stopped and calling it unguarded would report success having done
nothing: a VM with no IPSW configured, and a VM that is not stopped, each
fail with an error naming the cause.
Verified against main on Xcode 26.6 / Apple Silicon: no confirmation
sheet at any point, a macOS 26.6.2 install run to completion (21 GB,
framework returned success), the IPSW cleared from the registry
afterwards, and a subsequent `start` opening a display window with no
reinstall prompt and the guest booting to Setup Assistant.
UTMScripting.swift gains the install() method that bridge-gen.sh
generates for this sdef change.
Refs #6092
Assisted-by: Claude:claude-opus-5
Follow-up to the vmnet networking modes:
- Rename "Shared Network (VM to VM)" to "NAT Network" (config value
"NATNetwork", scripting term "nat network") so the picker does not offer
two "Shared" modes that differ only by a suffix.
- Request a fixed subnet per mode outside of the ranges vmnet hands out by
default. Those ranges are also used by NAT attachments and QEMU, so guest
addresses depended on what else was running whenever the network was
(re)created. Fall back to a vmnet chosen subnet when ours is taken.
- Show the mode description with DetailedSection. The bare Text row widened
the form and clipped the labels and controls in the settings window.
- Only require macOS 26 when a network is actually created, so a VM using
one of these modes can still be edited and saved on older hosts.
- Do not apply locale digit grouping to the vmnet error code.
- Fold the validation flag into the existing one that skipped drives and
leave the device unattached instead of using a NAT placeholder, remove
the unused reverse mapping, key the networks by vmnet_mode_t, and isolate
the manager to the main actor in place of a lock with no concurrent users.
Use .1 as the host-side address within each preferred /24. As reproduced
in PR #7865, vmnet accepts .0 during network creation but the interface
fails later at startup.
Assisted-by: Claude:claude-fable-5-1
Assisted-by: ChatGPT:GPT-6-Astra-Pro
No delegate callback follows a failed start, restore, or install, so the
VZVirtualMachine created for the attempt was kept until the next start and
the serial port PTYs stayed open and advertised for a stopped VM. With vmnet
networking this also kept the network and its subnet reserved with nothing
running. Drop the virtual machine and close the serial ports in that case,
unless the guest is still alive and will report its own stop.
Assisted-by: Claude:claude-fable-5-1
Apple Virtualization's existing Shared Network uses a separate NAT
attachment per VM, so guests cannot use it to reach each other. macOS 26
adds VZVmnetNetworkDeviceAttachment, which lets multiple VMs attach to the
same process-owned vmnet network.
Add two macOS 26+ modes:
- "Shared Network (VM to VM)": guests share a NAT-backed vmnet network and
can reach each other, the host, and external networks.
- "Host Only": guests share a host-only vmnet network and can reach each
other and the host without external routing.
UTMAppleVmnetNetworkManager keeps a weak attachment cache per mode while
Virtualization configuration and runtime objects own the attachment and its
native network. The network therefore follows the actual VZ object lifetime
rather than UTM's presentation state, including failure paths where a VZ VM
can remain alive after UTM reports it stopped. Configuration validation uses
a side-effect-free placeholder so saving a VM does not create a live vmnet
network.
Hide both modes before macOS 26 and add matching scripting enumerators and
round-trip parsing.
Resolves#6975.
Related to #7418 and #7380.
Assisted-by: Claude:claude-fable-5-1
Assisted-by: ChatGPT:GPT-6-Pro
Assisted-by: ChatGPT:GPT-5.6-Sol
macOS 26 draws a single Liquid Glass background behind a run of adjacent
toolbar items, so stop, start/pause, and restart read as one undivided
button. A space item only splits that background into separate groups.
The divider Safari's back/forward and Xcode's run/stop show comes from a
segmented control inside one toolbar item, so the nib now defines the
three controls as segments of one.
Clicking the stop segment still stops the VM, and clicking and holding
it still shows the shutdown menu, which now hangs off the segment rather
than an NSMenuToolbarItem. Enabling moves to the individual segments.
Segment tooltips are localizable, so the existing translations move from
the removed items to the segments. An autosaved toolbar item list that
names the removed items falls back to the nib default, so existing
installs pick up the new control.
Segments have no label, so VoiceOver reads their image descriptions.
The stop and restart images get localized descriptions the way the USB
item's image does, and start/pause already had them. German lacked a
"Restart" translation, so the one the removed toolbar item carried moves
to Localizable.strings, where the main menu's Restart item uses it too.
The group's "Power" label reuses the existing Chinese translations.
Assisted-by: Claude:claude-opus-5
The `connect` and `disconnect` USB commands and the application's list of USB
devices only worked with QEMU VMs because they were tied to CocoaSpice
objects. Now that Apple Virtualization VMs support USB passthrough on macOS
27, the scripting object wraps a small protocol adopted by both backends and
dispatches on the type of the target VM. The QEMU list is preferred when a
QEMU VM with USB sharing is running, otherwise the AccessoryAccess list is
returned so scripts work the same for both backends.
Assisted-by: Claude:claude-fable-5-1
Apple Virtualization VMs could not share host USB devices. macOS 27 adds
public APIs for this: AccessoryAccess.framework reports devices the user
assigned to UTM in the system "Virtual Machine Accessories" menu bar item and
VZUSBPassthroughDevice captures them, so the private API approach from the
abandoned branch is no longer needed.
Devices are reported once per process to UTMAppleUSBManager. A newly assigned
device is connected to the only running VM, or to the VM whose window is in
front when several are running, so the user does not have to select it twice.
The USB menu of the Apple VM window lists assigned devices and can move a
device from another VM after confirmation. A device the user disconnects from
the menu stays assigned to UTM but is not connected again until asked, since
the system reports it again right after it is detached. The VM label is set so
the system menu names the VM.
Connected devices are recorded with their UUID when the VM state is saved and
added to the configuration before the state is restored, so the guest keeps
the device across a suspend. A device that is missing at restore is skipped
and the guest sees it unplugged; the restore itself still succeeds.
Requires the com.apple.developer.accessory-access.usb entitlement.
Assisted-by: Claude:claude-fable-5-1
From iOS 26 the arrow edge passed to popoverTip is only a preference.
When the VM toolbar spans most of the screen width, the system attaches
the "Tap to hide/show toolbar" tip beside the chevron instead of below
it, covering the other toolbar buttons until the tip is dismissed. This
happened on iPhone on iOS 26.1 and on iPhone and iPad on iOS 27.
On iOS 26 and later, restrict the tip's arrow to the edge facing the
toolbar, using the top edge when the toolbar is docked at the top and
the bottom edge when it is docked at the bottom, so the tip always opens
toward the middle of the screen. iOS 17 through 25 keep the existing
behavior.
Assisted-by: Claude:claude-opus-5
Building with the iOS 27 SDK broke the VM toolbar on both iOS 26.1 and
iOS 27. On iOS 27 `.buttonStyle(.glass)` with a circle border shape sizes
each button from its label, so narrow glyphs got smaller circles than the
menus and the implicit gap iOS 26 added between buttons disappeared. On
iOS 26.1 the glass adapts to the backdrop and turns almost black over the
VM display, leaving only a faint outline of each button.
Replace the system glass button style with a custom style that draws the
icon in the same fixed frame as the pre-iOS 26 style and applies
interactive glass in a circle. A neutral gray tint with white icons keeps
the buttons readable over both dark and light guest content on either
runtime. Stack spacing no longer depends on the OS version, and the
pre-iOS 26 path is unchanged.
Assisted-by: Claude:claude-fable-5-1
Apps built with the iOS 27 SDK are no longer offered
UIWindowSceneSessionRoleExternalDisplayNonInteractive scenes from the
Info.plist scene manifest, so on iOS 27 the external display stayed black
while the VM ran on the device. Offer UTMSingleWindowView through the
SwiftUI scene accessory API instead; iOS 26 and earlier keep using the
Info.plist configuration and UTMExternalSceneDelegate.
The delegate now checks for the NonInteractive role explicitly, which is
what sessions report since iOS 16, instead of the deprecated constant.
Verified on the iPhone 18 Pro simulator (iOS 27.0) with the TVOut screen
powered on via simctl: the placeholder window appears on connect and the
VM display renders once selected; iPad (A16) on iOS 26.1 is unchanged.
Assisted-by: Claude:claude-fable-5-1
On iOS 26, adjacent toolbar items in the same placement merge into a
single Liquid Glass capsule, so the create VM and donate buttons render
as one control with no visual break between the two unrelated actions.
Insert a fixed ToolbarSpacer to split them into their own capsules.
ToolbarSpacer is unavailable on visionOS, hence the platform guard.
Assisted-by: Claude:claude-opus-5[1m]
Since Xcode 26.1, actool ignores an .appiconset that shares its name with
an Icon Composer .icon and renders the icons for older OS versions from the
.icon instead. The undocumented --enable-icon-stack-fallback-generation flag
that used to turn this off is now parsed and discarded, so building with
Xcode 27 replaced our flat icons with generated ones on macOS 12-15, where we
prefer our own low resolution art.
Replacing AppIcon.icns is not enough because macOS reads the icon from
Assets.car before Tahoe as well. Instead compile AppIcon.appiconset into its
own catalog and merge its renditions into the built Assets.car with CoreUI,
leaving the icon stack renditions that macOS 26 uses in place.
The rendition format is undocumented, so the merge fails the build if the
catalog does not look the way we expect, and checks that the icons still
decode after writing.
Assisted-by: Claude:claude-opus-5[1m]
Now that the deployment targets are iOS 15 and macOS 12, every guard,
fallback and workaround for an older release is dead code that only
obscures what the supported path is. Remove them:
- Drop the `@available` attributes for iOS 15, macOS 11 and macOS 12, and
unwrap the matching `#available`/`#unavailable` guards in Swift and the
`@available`/`API_AVAILABLE`/`NS_AVAILABLE_IOS` ones in Objective-C.
- Delete the legacy-only views and helpers left unreachable by that:
NumberTextFieldOld, VMConfigNetworkPortForwardLegacyView,
VMContextMenuModifier.bodyBigSur, the nonbrokenSheet, uniqued,
customBadge, borderedList and legacySettingsToolbar shims, the
.unsupported BootloaderSelection case, and the iOS 14 sacrificial
version Section in the QEMU settings list.
- jb_has_cs_execseg_allow_unsigned() has returned false for every host
since iOS 14.4, so drop it along with the is_device_A12_or_newer()
helper that only it used. The entitlement parser no longer needs to
copy the blob either, since that copy existed only so the psychic paper
needle could be blanked in place.
- VMDisplayTerminalViewController tracked the keyboard frame by hand for
iOS 14. `useAutoLayout` is a constant that cannot be overridden, so
nothing ever registered the notification observers and only the
keyboardLayoutGuide path is reachable.
- VMConfigQEMUView's additionalArguments is now referenced on iOS only,
which leaves its macOS branch, and the macOS 11 ForEach crash
workaround inside it, unreachable.
- Raise the Cydia package floor and the postinst/prerm helpers to match
the new minimum.
Two of the removed guards dropped an error path rather than just a
branch. The shared directory `featureNotSupported` throw covered
VZVirtioFileSystemDeviceConfiguration and friends being macOS 12+, which
no longer applies to any supported host. The macOS guest
`platformUnsupported` throw when decoding a boot config on Intel only
fired on macOS 11; the Apple Silicon requirement it really guards is
enforced unconditionally in
UTMAppleConfigurationSystem.fillVZConfiguration, which both the start and
the save path go through.
Assisted-by: Claude:claude-opus-5
Xcode 27 no longer supports deploying to iOS 14 or macOS 11, and the
macOS 27 SDK breaks several dependency builds, so raise the minimum
versions and fix the dependency build:
- Bump IOS_SDKMINVER/MAC_SDKMINVER and the project deployment targets.
- Pass our deployment targets to the Hypervisor build, whose project
still targets iOS 14.
- Build ANGLE without -Werror; new clang warnings break it.
- Update MoltenVK to pick up the upstream minimum target bump and set
a visionOS minimum so it no longer inherits the SDK version.
- Patch QEMU apple-gfx to use PGCreateDeviceWithDescriptor, since
PGNewDeviceWithDescriptor is no longer exported by the macOS 27 SDK.
- Skip copying the IOKit headers on macOS where they are already in the
SDK and a second copy conflicts with the IOKit module.
- Export MACOSX_DEPLOYMENT_TARGET since libtool drops -target when
linking C++ libraries, which built libasprintf for macOS 27.
- Default CI to the installed Xcode.
Assisted-by: Claude:claude-opus-5
The scripting-targets entitlement names the target app as a dictionary key built from $(PRODUCT_BUNDLE_PREFIX:default=com.utmapp). Xcode expands it only when it signs, and the unsigned package re-signs from the raw file, so utmctl in an unsigned build was allowed to script an app with that literal name and no other: every command failed with "failed to get scripting definition" and an unrecognized selector. Expand the key before signing.
VoiceOver announces the file selection row as a Browse button even though users must interact with it to reach the actual button. Explicitly contain the child controls and label the group with the existing field title so its announced role matches its behavior.
Assisted-by: Codex:gpt-6
The legacy workflows/Build/badge.svg endpoint renders "no status" for
branch=main&event=push even though those runs exist. The
actions/workflows/build.yml/badge.svg form resolves them correctly.
Assisted-by: Claude:claude-fable-5-1