|
@@ -262,6 +262,131 @@ extension VMDisplayAppleWindowController {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+@objc extension VMDisplayAppleWindowController {
|
|
|
|
+ @IBAction override func drivesButtonPressed(_ sender: Any) {
|
|
|
|
+ let menu = NSMenu()
|
|
|
|
+ menu.autoenablesItems = false
|
|
|
|
+ let item = NSMenuItem()
|
|
|
|
+ item.title = NSLocalizedString("Querying drives status...", comment: "VMDisplayWindowController")
|
|
|
|
+ item.isEnabled = false
|
|
|
|
+ menu.addItem(item)
|
|
|
|
+ updateDrivesMenu(menu, drives: appleConfig.drives)
|
|
|
|
+ menu.popUp(positioning: nil, at: NSEvent.mouseLocation, in: nil)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @nonobjc func updateDrivesMenu(_ menu: NSMenu, drives: [UTMAppleConfigurationDrive]) {
|
|
|
|
+ menu.removeAllItems()
|
|
|
|
+ if drives.count == 0 {
|
|
|
|
+ let item = NSMenuItem()
|
|
|
|
+ item.title = NSLocalizedString("No drives connected.", comment: "VMDisplayWindowController")
|
|
|
|
+ item.isEnabled = false
|
|
|
|
+ menu.addItem(item)
|
|
|
|
+ }
|
|
|
|
+ if #available(macOS 15, *), appleConfig.system.boot.operatingSystem == .macOS {
|
|
|
|
+ let item = NSMenuItem()
|
|
|
|
+ item.title = NSLocalizedString("Install Guest Tools…", comment: "VMDisplayAppleWindowController")
|
|
|
|
+ item.isEnabled = true
|
|
|
|
+ item.target = self
|
|
|
|
+ item.action = #selector(installGuestTools)
|
|
|
|
+ menu.addItem(item)
|
|
|
|
+ }
|
|
|
|
+ for i in drives.indices {
|
|
|
|
+ let drive = drives[i]
|
|
|
|
+ if !drive.isExternal {
|
|
|
|
+ continue // skip non-disks
|
|
|
|
+ }
|
|
|
|
+ let item = NSMenuItem()
|
|
|
|
+ item.title = label(for: drive)
|
|
|
|
+ if !drive.isExternal {
|
|
|
|
+ item.isEnabled = false
|
|
|
|
+ } else if #available(macOS 15, *) {
|
|
|
|
+ let submenu = NSMenu()
|
|
|
|
+ submenu.autoenablesItems = false
|
|
|
|
+ let eject = NSMenuItem(title: NSLocalizedString("Eject", comment: "VMDisplayWindowController"),
|
|
|
|
+ action: #selector(ejectDrive),
|
|
|
|
+ keyEquivalent: "")
|
|
|
|
+ eject.target = self
|
|
|
|
+ eject.tag = i
|
|
|
|
+ eject.isEnabled = drive.imageURL != nil
|
|
|
|
+ submenu.addItem(eject)
|
|
|
|
+ let change = NSMenuItem(title: NSLocalizedString("Change", comment: "VMDisplayWindowController"),
|
|
|
|
+ action: #selector(changeDriveImage),
|
|
|
|
+ keyEquivalent: "")
|
|
|
|
+ change.target = self
|
|
|
|
+ change.tag = i
|
|
|
|
+ change.isEnabled = true
|
|
|
|
+ submenu.addItem(change)
|
|
|
|
+ item.submenu = submenu
|
|
|
|
+ }
|
|
|
|
+ menu.addItem(item)
|
|
|
|
+ }
|
|
|
|
+ menu.update()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @available(macOS 15, *)
|
|
|
|
+ func ejectDrive(sender: AnyObject) {
|
|
|
|
+ guard let menu = sender as? NSMenuItem else {
|
|
|
|
+ logger.error("wrong sender for ejectDrive")
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ let drive = appleConfig.drives[menu.tag]
|
|
|
|
+ Task.detached(priority: .background) { [self] in
|
|
|
|
+ do {
|
|
|
|
+ try await appleVM.eject(drive)
|
|
|
|
+ } catch {
|
|
|
|
+ Task { @MainActor in
|
|
|
|
+ showErrorAlert(error.localizedDescription)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @available(macOS 15, *)
|
|
|
|
+ func openDriveImage(forDriveIndex index: Int) {
|
|
|
|
+ let drive = appleConfig.drives[index]
|
|
|
|
+ let openPanel = NSOpenPanel()
|
|
|
|
+ openPanel.title = NSLocalizedString("Select Drive Image", comment: "VMDisplayWindowController")
|
|
|
|
+ openPanel.allowedContentTypes = [.data]
|
|
|
|
+ openPanel.beginSheetModal(for: window!) { response in
|
|
|
|
+ guard response == .OK else {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ guard let url = openPanel.url else {
|
|
|
|
+ logger.debug("no file selected")
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ Task.detached(priority: .background) { [self] in
|
|
|
|
+ do {
|
|
|
|
+ try await appleVM.changeMedium(drive, to: url)
|
|
|
|
+ } catch {
|
|
|
|
+ Task { @MainActor in
|
|
|
|
+ showErrorAlert(error.localizedDescription)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @available(macOS 15, *)
|
|
|
|
+ func changeDriveImage(sender: AnyObject) {
|
|
|
|
+ guard let menu = sender as? NSMenuItem else {
|
|
|
|
+ logger.error("wrong sender for ejectDrive")
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ openDriveImage(forDriveIndex: menu.tag)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @nonobjc private func label(for drive: UTMAppleConfigurationDrive) -> String {
|
|
|
|
+ let imageURL = drive.imageURL
|
|
|
|
+ return String.localizedStringWithFormat(NSLocalizedString("USB Mass Storage: %@", comment: "VMDisplayAppleDisplayController"),
|
|
|
|
+ imageURL?.lastPathComponent ?? NSLocalizedString("none", comment: "VMDisplayAppleDisplayController"))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @available(macOS 15, *)
|
|
|
|
+ @MainActor private func installGuestTools(sender: AnyObject) {
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
extension VMDisplayAppleWindowController: UTMScreenshotProvider {
|
|
extension VMDisplayAppleWindowController: UTMScreenshotProvider {
|
|
var screenshot: UTMVirtualMachineScreenshot? {
|
|
var screenshot: UTMVirtualMachineScreenshot? {
|
|
if let image = mainView?.image() {
|
|
if let image = mainView?.image() {
|