mirror of
https://github.com/somegeekintn/SimDirs.git
synced 2026-03-25 08:55:54 +00:00
Add ability to record screen although not 100% satisfied with it.
This commit is contained in:
parent
7b02bd3da2
commit
4d67317ebb
4 changed files with 93 additions and 20 deletions
|
|
@ -8,14 +8,24 @@
|
|||
import Foundation
|
||||
|
||||
struct SimCtl {
|
||||
func run(args: [String]) throws -> Data {
|
||||
func run(args: [String], run: Bool = true) throws -> Process {
|
||||
let process = Process()
|
||||
let pipe = Pipe()
|
||||
|
||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/xcrun")
|
||||
process.arguments = ["simctl"] + args
|
||||
process.standardOutput = pipe
|
||||
process.standardError = nil
|
||||
if run {
|
||||
try process.run()
|
||||
}
|
||||
|
||||
return process
|
||||
}
|
||||
|
||||
func run(args: [String]) throws -> Data {
|
||||
let process : Process = try run(args: args, run: false)
|
||||
let pipe = Pipe()
|
||||
|
||||
process.standardOutput = pipe
|
||||
try process.run()
|
||||
|
||||
return pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
|
|
@ -119,4 +129,8 @@ struct SimCtl {
|
|||
func saveScreen(_ device: SimDevice, url: URL) throws {
|
||||
try runAsync(args: ["io", device.udid, "screenshot", url.path])
|
||||
}
|
||||
|
||||
func saveVideo(_ device: SimDevice, url: URL) throws -> Process {
|
||||
return try run(args: ["io", device.udid, "recordVideo", "--force", url.path])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ class SimDevice: ObservableObject, Decodable {
|
|||
@Published var appearance = Appearance.unknown
|
||||
@Published var contentSize = ContentSize.unknown
|
||||
@Published var increaseContrast = IncreaseContrast.unknown
|
||||
@Published var isRecording = false
|
||||
var recordingProcess : Process?
|
||||
var isTransitioning : Bool { state == .booting || state == .shuttingDown }
|
||||
var isBooted : Bool {
|
||||
get { state.showBooted == true }
|
||||
|
|
@ -190,6 +192,28 @@ class SimDevice: ObservableObject, Decodable {
|
|||
print("Failed to save screen: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func saveVideo(_ url: URL) {
|
||||
do {
|
||||
recordingProcess = try SimCtl().saveVideo(self, url: url)
|
||||
if recordingProcess != nil {
|
||||
isRecording = true
|
||||
}
|
||||
} catch {
|
||||
print("Failed to save video: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func endRecording() {
|
||||
if let process = recordingProcess {
|
||||
process.interrupt()
|
||||
recordingProcess = nil
|
||||
isRecording = false
|
||||
}
|
||||
else {
|
||||
isRecording = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SimDevice {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension SimDevice {
|
||||
public var content : some View { DeviceContent(self) }
|
||||
|
|
@ -45,6 +46,25 @@ extension SimDevice.IncreaseContrast: ToggleDescriptor {
|
|||
}
|
||||
|
||||
struct DeviceContent: View {
|
||||
enum SaveType {
|
||||
case image
|
||||
case video
|
||||
|
||||
var allowedContentTypes : [UTType] {
|
||||
switch self {
|
||||
case .image: return [.png]
|
||||
case .video: return [.mpeg4Movie]
|
||||
}
|
||||
}
|
||||
|
||||
var title : String {
|
||||
switch self {
|
||||
case .image: return "Save Screen"
|
||||
case .video: return "Save Recording"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ObservedObject var device : SimDevice
|
||||
@State var isBooted : Bool
|
||||
|
||||
|
|
@ -77,6 +97,23 @@ struct DeviceContent: View {
|
|||
.font(.subheadline)
|
||||
.textSelection(.enabled)
|
||||
|
||||
ContentHeader("Actions")
|
||||
HStack(spacing: 16) {
|
||||
Button(action: { saveScreen(.image) }) {
|
||||
Text("Save Screen")
|
||||
.fontWeight(.semibold)
|
||||
.font(.system(size: 11))
|
||||
}
|
||||
.buttonStyle(.systemIcon("camera.on.rectangle"))
|
||||
|
||||
Button(action: { device.isRecording ? device.endRecording() : saveScreen(.video) }) {
|
||||
Text(device.isRecording ? "End Recording" : "Record Screen")
|
||||
.fontWeight(.semibold)
|
||||
.font(.system(size: 11))
|
||||
}
|
||||
.buttonStyle(.systemIcon("record.circle", active: device.isRecording))
|
||||
}
|
||||
|
||||
ContentHeader("UI")
|
||||
HStack(spacing: 16) {
|
||||
if device.appearance != .unsupported {
|
||||
|
|
@ -99,14 +136,6 @@ struct DeviceContent: View {
|
|||
.opacity(isBooted ? 1.0 : 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
ContentHeader("Actions")
|
||||
Button(action: saveScreen) {
|
||||
Text("Save Screen")
|
||||
.fontWeight(.semibold)
|
||||
.font(.system(size: 11))
|
||||
}
|
||||
.buttonStyle(.systemIcon("camera.on.rectangle"))
|
||||
}
|
||||
.environment(\.isEnabled, isBooted)
|
||||
.onAppear {
|
||||
|
|
@ -124,21 +153,24 @@ struct DeviceContent: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveScreen() {
|
||||
|
||||
func saveScreen(_ type: SaveType = .image) {
|
||||
let savePanel = NSSavePanel()
|
||||
|
||||
savePanel.allowedContentTypes = [.png]
|
||||
savePanel.allowedContentTypes = type.allowedContentTypes
|
||||
savePanel.canCreateDirectories = true
|
||||
savePanel.isExtensionHidden = false
|
||||
savePanel.title = "Save Screen"
|
||||
savePanel.title = type.title
|
||||
savePanel.message = "Select destination"
|
||||
savePanel.nameFieldLabel = "Filename:"
|
||||
savePanel.nameFieldStringValue = "\(device.name) - \(fileDateFormatter.string(from: Date()))"
|
||||
|
||||
if savePanel.runModal() == .OK {
|
||||
if let url = savePanel.url {
|
||||
device.saveScreen(url)
|
||||
switch type {
|
||||
case .image: device.saveScreen(url)
|
||||
case .video: device.saveVideo(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,17 +8,19 @@
|
|||
import SwiftUI
|
||||
|
||||
extension ButtonStyle where Self == SystemIconButtonStyle {
|
||||
static func systemIcon(_ imageName: String) -> SystemIconButtonStyle {
|
||||
SystemIconButtonStyle(imageName)
|
||||
static func systemIcon(_ imageName: String, active: Bool = false) -> SystemIconButtonStyle {
|
||||
SystemIconButtonStyle(imageName, active: active)
|
||||
}
|
||||
}
|
||||
|
||||
struct SystemIconButtonStyle: ButtonStyle {
|
||||
@State var isFocused = false
|
||||
let isActive : Bool
|
||||
let imageName : String
|
||||
|
||||
init(_ imageName: String) {
|
||||
init(_ imageName: String, active: Bool = false) {
|
||||
self.imageName = imageName
|
||||
self.isActive = active
|
||||
}
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
|
|
@ -32,11 +34,12 @@ struct SystemIconButtonStyle: ButtonStyle {
|
|||
.resizable()
|
||||
.aspectRatio(contentMode: ContentMode.fit)
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(isActive ? .accentColor : foregroundColor(pressed: configuration.isPressed))
|
||||
}
|
||||
|
||||
configuration.label
|
||||
.foregroundColor(foregroundColor(pressed: configuration.isPressed))
|
||||
}
|
||||
.foregroundColor(foregroundColor(pressed: configuration.isPressed))
|
||||
.padding(1.0)
|
||||
.onHover { isFocused = $0 }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue