Skip to content

Commit

Permalink
vm: get screenshot PNG data early
Browse files Browse the repository at this point in the history
Fixes #4009
  • Loading branch information
osy committed Feb 25, 2024
1 parent 07650fa commit da9c5c4
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 33 deletions.
4 changes: 2 additions & 2 deletions Platform/VMData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ import SwiftUI
@Published var state: UTMVirtualMachineState = .stopped

/// Copy from wrapped VM
@Published var screenshot: PlatformImage?
@Published var screenshot: UTMVirtualMachineScreenshot?

/// Allows changes in the config, registry, and VM to be reflected
private var observers: [AnyCancellable] = []
Expand Down Expand Up @@ -426,7 +426,7 @@ extension VMData {

/// If non-null, is the most recent screenshot image of the running VM
var screenshotImage: PlatformImage? {
wrapped?.screenshot
wrapped?.screenshot?.image
}
}

Expand Down
4 changes: 2 additions & 2 deletions Platform/macOS/Display/VMDisplayAppleWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,9 @@ extension VMDisplayAppleWindowController {
}

extension VMDisplayAppleWindowController: UTMScreenshotProvider {
var screenshot: PlatformImage? {
var screenshot: UTMVirtualMachineScreenshot? {
if let image = mainView?.image() {
return image
return UTMVirtualMachineScreenshot(wrapping: image)
} else {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class VMDisplayQemuMetalWindowController: VMDisplayQemuWindowController {
override func enterSuspended(isBusy busy: Bool) {
if !busy {
metalView.isHidden = true
screenshotView.image = vm.screenshot
screenshotView.image = vm.screenshot?.image
screenshotView.isHidden = false
}
if vm.state == .stopped {
Expand Down
6 changes: 3 additions & 3 deletions Remote/UTMRemoteSpiceVirtualMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ final class UTMRemoteSpiceVirtualMachine: UTMSpiceVirtualMachine {
}
}

var screenshot: PlatformImage? {
var screenshot: UTMVirtualMachineScreenshot? {
willSet {
onStateChange?()
}
Expand Down Expand Up @@ -273,11 +273,11 @@ extension UTMRemoteSpiceVirtualMachine {
}

func loadScreenshot(from url: URL) {
screenshot = UIImage(contentsOfURL: url)
screenshot = UTMVirtualMachineScreenshot(contentsOfURL: url)
}

func saveScreenshot() async {
if let data = screenshot?.pngData() {
if let data = screenshot?.pngData {
try? await server.sendPackageFile(for: id, relativePathComponents: [kUTMBundleScreenshotFilename], data: data)
}
}
Expand Down
4 changes: 2 additions & 2 deletions Services/UTMAppleVirtualMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ final class UTMAppleVirtualMachine: UTMVirtualMachine {
}
}

private(set) var screenshot: PlatformImage? {
private(set) var screenshot: UTMVirtualMachineScreenshot? {
willSet {
onStateChange?()
}
Expand Down Expand Up @@ -729,7 +729,7 @@ extension UTMAppleVirtualMachine: VZVirtualMachineDelegate {
}

protocol UTMScreenshotProvider: AnyObject {
var screenshot: PlatformImage? { get }
var screenshot: UTMVirtualMachineScreenshot? { get }
}

enum UTMAppleVirtualMachineError: Error {
Expand Down
2 changes: 1 addition & 1 deletion Services/UTMQemuVirtualMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ final class UTMQemuVirtualMachine: UTMSpiceVirtualMachine {
}
}

var screenshot: PlatformImage? {
var screenshot: UTMVirtualMachineScreenshot? {
willSet {
onStateChange?()
}
Expand Down
7 changes: 4 additions & 3 deletions Services/UTMSpiceVirtualMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protocol UTMSpiceVirtualMachine: UTMVirtualMachine where Configuration == UTMQem
var isRunningAsDisposible: Bool { get }

/// Get and set screenshot
var screenshot: PlatformImage? { get set }
var screenshot: UTMVirtualMachineScreenshot? { get set }

/// Handles IO
var ioServiceDelegate: UTMSpiceIODelegate? { get set }
Expand Down Expand Up @@ -72,8 +72,9 @@ extension UTMSpiceVirtualMachine {
extension UTMSpiceVirtualMachine {
@MainActor @discardableResult
func takeScreenshot() async -> Bool {
let screenshot = await ioService?.screenshot()
self.screenshot = screenshot?.image
if let screenshot = await ioService?.screenshot() {
self.screenshot = UTMVirtualMachineScreenshot(wrapping: screenshot.image)
}
return true
}

Expand Down
61 changes: 42 additions & 19 deletions Services/UTMVirtualMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ protocol UTMVirtualMachine: AnyObject, Identifiable {
var state: UTMVirtualMachineState { get }

/// If non-null, is the most recent screenshot of the running VM
var screenshot: PlatformImage? { get }
var screenshot: UTMVirtualMachineScreenshot? { get }

/// If non-null, `saveSnapshot` and `restoreSnapshot` will not work due to the reason specified
var snapshotUnsupportedError: Error? { get }

Expand Down Expand Up @@ -290,6 +290,43 @@ extension UTMVirtualMachine {

// MARK: - Screenshot

struct UTMVirtualMachineScreenshot {
let image: PlatformImage
let pngData: Data?

init?(contentsOfURL url: URL) {
#if canImport(AppKit)
guard let image = NSImage(contentsOf: url) else {
return nil
}
#elseif canImport(UIKit)
guard let image = UIImage(contentsOfURL: url) else {
return nil
}
#endif
self.image = image
self.pngData = Self.createData(from: image)
}

init(wrapping image: PlatformImage) {
self.image = image
self.pngData = Self.createData(from: image)
}

private static func createData(from image: PlatformImage) -> Data? {
#if canImport(AppKit)
guard let cgref = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
return nil
}
let newrep = NSBitmapImageRep(cgImage: cgref)
newrep.size = image.size
return newrep.representation(using: .png, properties: [:])
#elseif canImport(UIKit)
return image.pngData()
#endif
}
}

extension UTMVirtualMachine {
private var isScreenshotSaveEnabled: Bool {
!UserDefaults.standard.bool(forKey: "NoSaveScreenshot")
Expand Down Expand Up @@ -319,12 +356,8 @@ extension UTMVirtualMachine {
return timer
}

func loadScreenshot() -> PlatformImage? {
#if canImport(AppKit)
return NSImage(contentsOf: screenshotUrl)
#elseif canImport(UIKit)
return UIImage(contentsOfURL: screenshotUrl)
#endif
func loadScreenshot() -> UTMVirtualMachineScreenshot? {
UTMVirtualMachineScreenshot(contentsOfURL: screenshotUrl)
}

func saveScreenshot() throws {
Expand All @@ -334,17 +367,7 @@ extension UTMVirtualMachine {
guard let screenshot = screenshot else {
return
}
#if canImport(AppKit)
guard let cgref = screenshot.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
return
}
let newrep = NSBitmapImageRep(cgImage: cgref)
newrep.size = screenshot.size
let pngdata = newrep.representation(using: .png, properties: [:])
try pngdata?.write(to: screenshotUrl)
#elseif canImport(UIKit)
try screenshot.pngData()?.write(to: screenshotUrl)
#endif
try screenshot.pngData?.write(to: screenshotUrl)
}

func deleteScreenshot() throws {
Expand Down

0 comments on commit da9c5c4

Please sign in to comment.