diff --git a/example/macOS/installer.go b/example/macOS/installer.go index 2d7fd79..b73c954 100644 --- a/example/macOS/installer.go +++ b/example/macOS/installer.go @@ -3,13 +3,26 @@ package main import ( "context" "fmt" + "os" "time" "github.com/Code-Hex/vz/v2" ) func installMacOS(ctx context.Context) error { + if err := CreateVMBundle(); err != nil { + return fmt.Errorf("failed to VM.bundle in home directory: %w", err) + } + restoreImagePath := GetRestoreImagePath() + if _, err := os.Stat(restoreImagePath); err != nil { + if !os.IsNotExist(err) { + return err + } + if err := downloadRestoreImage(ctx, restoreImagePath); err != nil { + return fmt.Errorf("failed to download restore image: %w", err) + } + } restoreImage, err := vz.LoadMacOSRestoreImageFromPath(restoreImagePath) if err != nil { return fmt.Errorf("failed to load restore image: %w", err) @@ -46,7 +59,7 @@ func installMacOS(ctx context.Context) error { fmt.Println("install has been completed") return case <-ticker.C: - fmt.Printf("install: %d\r", int(installer.FractionCompleted()*100)) + fmt.Printf("install: %.3f%%\r", installer.FractionCompleted()*100) } } }() @@ -95,3 +108,26 @@ func createMacInstallerPlatformConfiguration(macOSConfiguration *vz.MacOSConfigu vz.WithMachineIdentifier(machineIdentifier), ) } + +func downloadRestoreImage(ctx context.Context, destPath string) error { + progress, err := vz.FetchLatestSupportedMacOSRestoreImage(ctx, destPath) + if err != nil { + return err + } + + fmt.Printf("download restore image in %q\n", destPath) + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + fmt.Println("download has been cancelled") + return ctx.Err() + case <-progress.Finished(): + fmt.Println("download has been completed") + return progress.Err() + case <-ticker.C: + fmt.Printf("download: %.3f%%\r", progress.FractionCompleted()*100) + } + } +} diff --git a/example/macOS/main.go b/example/macOS/main.go index 33a90e8..82887f1 100644 --- a/example/macOS/main.go +++ b/example/macOS/main.go @@ -173,10 +173,6 @@ func createNetworkDeviceConfiguration() (*vz.VirtioNetworkDeviceConfiguration, e return vz.NewVirtioNetworkDeviceConfiguration(natAttachment) } -func createPointingDeviceConfiguration() (*vz.USBScreenCoordinatePointingDeviceConfiguration, error) { - return vz.NewUSBScreenCoordinatePointingDeviceConfiguration() -} - func createKeyboardConfiguration() (*vz.USBKeyboardConfiguration, error) { return vz.NewUSBKeyboardConfiguration() } @@ -262,13 +258,17 @@ func setupVMConfiguration(platformConfig vz.PlatformConfiguration) (*vz.VirtualM networkDeviceConfig, }) - pointingDeviceConfig, err := createPointingDeviceConfiguration() + usbScreenPointingDevice, err := vz.NewUSBScreenCoordinatePointingDeviceConfiguration() if err != nil { return nil, fmt.Errorf("failed to create pointing device configuration: %w", err) } - config.SetPointingDevicesVirtualMachineConfiguration([]vz.PointingDeviceConfiguration{ - pointingDeviceConfig, - }) + pointingDevices := []vz.PointingDeviceConfiguration{usbScreenPointingDevice} + + trackpad, err := vz.NewMacTrackpadConfiguration() + if err == nil { + pointingDevices = append(pointingDevices, trackpad) + } + config.SetPointingDevicesVirtualMachineConfiguration(pointingDevices) keyboardDeviceConfig, err := createKeyboardConfiguration() if err != nil { diff --git a/pointing_device_arm64.go b/pointing_device_arm64.go new file mode 100644 index 0000000..1b80047 --- /dev/null +++ b/pointing_device_arm64.go @@ -0,0 +1,49 @@ +//go:build darwin && arm64 +// +build darwin,arm64 + +package vz + +/* +#cgo darwin CFLAGS: -x objective-c -fno-objc-arc +#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization +# include "virtualization_13_arm64.h" +*/ +import "C" +import "runtime" + +// MacTrackpadConfiguration is a struct that defines the configuration +// for a Mac trackpad. +// +// This device is only recognized by virtual machines running macOS 13.0 and later. +// In order to support both macOS 13.0 and earlier guests, VirtualMachineConfiguration.pointingDevices +// can be set to an array containing both a MacTrackpadConfiguration and +// a USBScreenCoordinatePointingDeviceConfiguration object. macOS 13.0 and later guests will use +// the multi-touch trackpad device, while earlier versions of macOS will use the USB pointing device. +// +// see: https://developer.apple.com/documentation/virtualization/vzmactrackpadconfiguration?language=objc +type MacTrackpadConfiguration struct { + pointer + + *basePointingDeviceConfiguration +} + +var _ PointingDeviceConfiguration = (*MacTrackpadConfiguration)(nil) + +// NewMacTrackpadConfiguration creates a new MacTrackpadConfiguration. +// +// This is only supported on macOS 13 and newer, ErrUnsupportedOSVersion will +// be returned on older versions. +func NewMacTrackpadConfiguration() (*MacTrackpadConfiguration, error) { + if macosMajorVersionLessThan(13) { + return nil, ErrUnsupportedOSVersion + } + config := &MacTrackpadConfiguration{ + pointer: pointer{ + ptr: C.newVZMacTrackpadConfiguration(), + }, + } + runtime.SetFinalizer(config, func(self *MacTrackpadConfiguration) { + self.Release() + }) + return config, nil +} diff --git a/shared_directory.go b/shared_directory.go index e0e66e1..e53a54b 100644 --- a/shared_directory.go +++ b/shared_directory.go @@ -170,3 +170,16 @@ func NewMultipleDirectoryShare(shares map[string]*SharedDirectory) (*MultipleDir }) return config, nil } + +// MacOSGuestAutomountTag returns the macOS automount tag. +// +// A device configured with this tag will be automatically mounted in a macOS guest. +// This is only supported on macOS 13 and newer, ErrUnsupportedOSVersion will +// be returned on older versions. +func MacOSGuestAutomountTag() (string, error) { + if macosMajorVersionLessThan(13) { + return "", ErrUnsupportedOSVersion + } + cstring := (*char)(C.getMacOSGuestAutomountTag()) + return cstring.String(), nil +} diff --git a/virtualization_13.h b/virtualization_13.h index cffef8d..9644b9c 100644 --- a/virtualization_13.h +++ b/virtualization_13.h @@ -41,4 +41,6 @@ void *newVZSpiceAgentPortAttachment(); void setSharesClipboardVZSpiceAgentPortAttachment(void *attachment, bool sharesClipboard); const char *getSpiceAgentPortName(); -void startWithOptionsCompletionHandler(void *machine, void *queue, void *options, void *completionHandler); \ No newline at end of file +void startWithOptionsCompletionHandler(void *machine, void *queue, void *options, void *completionHandler); + +const char *getMacOSGuestAutomountTag(); \ No newline at end of file diff --git a/virtualization_13.m b/virtualization_13.m index 4d08c97..c7d4f33 100644 --- a/virtualization_13.m +++ b/virtualization_13.m @@ -438,4 +438,18 @@ void startWithOptionsCompletionHandler(void *machine, void *queue, void *options } #endif RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +/*! + @abstract The macOS automount tag. + @discussion A device configured with this tag will be automatically mounted in a macOS guest. + */ +const char *getMacOSGuestAutomountTag() +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + return [[VZVirtioFileSystemDeviceConfiguration macOSGuestAutomountTag] UTF8String]; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); } \ No newline at end of file diff --git a/virtualization_13_arm64.h b/virtualization_13_arm64.h index 068e4c4..4f79596 100644 --- a/virtualization_13_arm64.h +++ b/virtualization_13_arm64.h @@ -24,4 +24,6 @@ int availabilityVZLinuxRosettaDirectoryShare(); void *newVZMacOSVirtualMachineStartOptions(bool startUpFromMacOSRecovery); +void *newVZMacTrackpadConfiguration(); + #endif \ No newline at end of file diff --git a/virtualization_13_arm64.m b/virtualization_13_arm64.m index 1e4bd1d..79c1fdf 100644 --- a/virtualization_13_arm64.m +++ b/virtualization_13_arm64.m @@ -68,3 +68,22 @@ int availabilityVZLinuxRosettaDirectoryShare() #endif RAISE_UNSUPPORTED_MACOS_EXCEPTION(); } + +/*! + @abstract Configuration for a Mac trackpad. + @discussion + This device can be used by VZVirtualMachineView to send pointer events and multi-touch trackpad gestures to the virtual machine. + Note: this device is only recognized by virtual machines running macOS 13.0 and later. In order to support both macOS 13.0 and earlier + guests, VZVirtualMachineConfiguration.pointingDevices can be set to an array containing both a VZMacTrackpadConfiguration and + a VZUSBScreenCoordinatePointingDeviceConfiguration object. macOS 13.0 and later guests will use the multi-touch trackpad device, + while earlier versions of macOS will use the USB pointing device. + */ +void *newVZMacTrackpadConfiguration() +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + return [[VZMacTrackpadConfiguration alloc] init]; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} \ No newline at end of file diff --git a/virtualization_arm64.go b/virtualization_arm64.go index 511d86d..796dd41 100644 --- a/virtualization_arm64.go +++ b/virtualization_arm64.go @@ -216,9 +216,6 @@ func NewMacAuxiliaryStorage(storagePath string, opts ...NewMacAuxiliaryStorageOp if macosMajorVersionLessThan(12) { return nil, ErrUnsupportedOSVersion } - if _, err := os.Stat(storagePath); err != nil { - return nil, err - } storage := &MacAuxiliaryStorage{storagePath: storagePath} for _, opt := range opts {