diff --git a/CaptureSample/CaptureEngine.swift b/CaptureSample/CaptureEngine.swift index 0633c8e..ae66243 100644 --- a/CaptureSample/CaptureEngine.swift +++ b/CaptureSample/CaptureEngine.swift @@ -76,10 +76,18 @@ class CaptureEngine: @unchecked Sendable { powerMeter.processSilence() } - func startRecording(options: Options) async throws { + func startEncodingSession(options: Options) async throws { self.streamOutput.encoder = try await VTEncoder(options: options) } + func startRecording(options: Options) async throws { + if self.streamOutput.encoder == nil { + self.streamOutput.encoder = try await VTEncoder(options: options) + } else { + self.streamOutput.encoder.startRecording() + } + } + func stopRecording() async throws { try await self.streamOutput.stopReplayBuffer() try await self.streamOutput.encoder.stopEncoding() @@ -169,9 +177,9 @@ class CaptureEngineStreamOutput: NSObject, SCStreamOutput, SCStreamDelegate { } } - func stopReplayBuffer() { + func stopReplayBuffer() async { do { - try self.encoder.videoSink.stopReplayBuffer() + try await self.encoder.videoSink.stopReplayBuffer() } catch { self.errorHandler!(error) } diff --git a/CaptureSample/CaptureSample.entitlements b/CaptureSample/CaptureSample.entitlements index 15eb2fd..a1e1364 100644 --- a/CaptureSample/CaptureSample.entitlements +++ b/CaptureSample/CaptureSample.entitlements @@ -2,10 +2,12 @@ - com.apple.security.app-sandbox + com.apple.developer.system-extension.install - com.apple.security.assets.movies.read-write + com.apple.security.app-sandbox + com.apple.security.application-groups + com.apple.security.files.user-selected.read-write diff --git a/CaptureSample/Encoder.swift b/CaptureSample/Encoder.swift index c13685d..bed9288 100644 --- a/CaptureSample/Encoder.swift +++ b/CaptureSample/Encoder.swift @@ -21,7 +21,7 @@ enum EncoderError: Error { case unknownFrameType } -class VTEncoder: NSObject { +class VTEncoder { var session: VTCompressionSession! var decodeSession: VTDecompressionSession! @@ -45,7 +45,6 @@ class VTEncoder: NSObject { init?(options: Options) async throws { self.destWidth = options.destWidth self.destHeight = options.destHeight - super.init() let sourceImageBufferAttributes = [kCVPixelBufferPixelFormatTypeKey: options.pixelFormat as CFNumber] as CFDictionary let err = VTCompressionSessionCreate(allocator: kCFAllocatorDefault, @@ -226,6 +225,10 @@ class VTEncoder: NSObject { } } + func startRecording() { + self.videoSink.makeActive() + } + func outputHandler(_ status: OSStatus, flags: VTDecodeInfoFlags, buffer: CVImageBuffer?, uhtime: CMTime, uhothertime: CMTime) { self.videoSink.mostRecentImageBuffer = buffer } diff --git a/CaptureSample/ScreenRecorder.swift b/CaptureSample/ScreenRecorder.swift index fd9ed3a..c924212 100644 --- a/CaptureSample/ScreenRecorder.swift +++ b/CaptureSample/ScreenRecorder.swift @@ -11,6 +11,8 @@ import Combine import OSLog import SwiftUI import AVFoundation +import com_jcm_record_RecordVirtualCam +import SystemExtensions /// A provider of audio levels from the captured samples. class AudioLevelsProvider: ObservableObject { @@ -19,9 +21,15 @@ class AudioLevelsProvider: ObservableObject { @MainActor class ScreenRecorder: ObservableObject { + + + + private var extensionActivated = false //MARK: event tap + //private var providerSource: RecordVirtualCamProviderSource! + var eventTap: RecordEventTap! = nil private let logger = Logger.capture @@ -136,6 +144,7 @@ class ScreenRecorder: ObservableObject { @Published var isRunning = false @Published var isRecording = false + @Published var isEncoding = false @Published var captureWidth: Int = 0 @Published var captureHeight: Int = 0 @@ -188,6 +197,16 @@ class ScreenRecorder: ObservableObject { didSet { updateEngine() } } + @Published var selectedApplications = Set() { + willSet { + print("setting selected applications \(newValue)") + } + didSet { + print("set selected applications \(selectedApplications)") + updateEngine() + } + } + @AppStorage("excludeSelf") var isAppExcluded = true { didSet { updateEngine() } } @@ -307,7 +326,7 @@ class ScreenRecorder: ObservableObject { CaptureSplitViewPreview() }() - private var availableApps = [SCRunningApplication]() + @Published var availableApps = [SCRunningApplication]() @Published private(set) var availableDisplays = [SCDisplay]() @Published private(set) var availableWindows = [SCWindow]() @@ -424,9 +443,25 @@ class ScreenRecorder: ObservableObject { isRunning = false } + func startEncodingSession() async { + guard isRunning else { return } + guard !isRecording else { return } + guard !isEncoding else { return } + self.options.logStart(logger) + do { + try await captureEngine.startEncodingSession(options: self.options) + //self.isRecording = true + } catch { + self.isRecording = false + self.errorText = error.localizedDescription + self.isShowingError = true + } + } + func record() async { guard isRunning else { return } guard !isRecording else { return } + self.options.logStart(logger) guard self.filePath != "" else { self.isRecording = false self.errorText = "No output folder selected." @@ -434,7 +469,6 @@ class ScreenRecorder: ObservableObject { self.isShowingError = true return } - self.options.logStart(logger) do { try await captureEngine.startRecording(options: self.options) self.isRecording = true @@ -487,6 +521,13 @@ class ScreenRecorder: ObservableObject { audioLevelsProvider.audioLevels = AudioLevels.zero }*/ + func updateEncodePreview() { + Task { + await self.stopRecord() + await self.startEncodingSession() + } + } + /// - Tag: UpdateCaptureConfig private func updateEngine() { guard isRunning else { return } @@ -503,8 +544,17 @@ class ScreenRecorder: ObservableObject { } self.eventTap?.callback = self.saveReplayBuffer if self.showsEncodePreview { - + self.updateEncodePreview() } + /*if !self.extensionActivated { + let identifier = "com.example.apple-samplecode.CustomCamera.CameraExtension" + + + // Submit an activation request. + let activationRequest = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: identifier, queue: .main) + activationRequest.delegate = self + OSSystemExtensionManager.shared.submitRequest(activationRequest) + }*/ } /// - Tag: UpdateFilter @@ -513,13 +563,11 @@ class ScreenRecorder: ObservableObject { switch captureType { case .display: guard let display = selectedDisplay else { fatalError("No display selected.") } - var excludedApps = [SCRunningApplication]() + var excludedApps = self.availableApps // If a user chooses to exclude the app from the stream, // exclude it by matching its bundle identifier. - if isAppExcluded { - excludedApps = availableApps.filter { app in - Bundle.main.bundleIdentifier == app.bundleIdentifier - } + excludedApps = availableApps.filter { app in + self.selectedApplications.contains(app.id) } // Create a content filter with excluded apps. filter = SCContentFilter(display: display, @@ -541,6 +589,12 @@ class ScreenRecorder: ObservableObject { // Configure audio capture. streamConfig.capturesAudio = isAudioCaptureEnabled streamConfig.excludesCurrentProcessAudio = isAppAudioExcluded + if #available(macOS 14.0, *) { + //streamConfig.capturesShadowsOnly = true + //streamConfig.ignoreGlobalClipDisplay = true + } else { + // Fallback on earlier versions + } // Configure the display content width and height. if captureType == .display, let display = selectedDisplay { @@ -643,3 +697,8 @@ extension SCDisplay { "Display: \(width) x \(height)" } } +extension SCRunningApplication: Identifiable { + public var id: String { + self.bundleIdentifier + } +} diff --git a/CaptureSample/VideoSink.swift b/CaptureSample/VideoSink.swift index c4f9c5c..9a70da8 100644 --- a/CaptureSample/VideoSink.swift +++ b/CaptureSample/VideoSink.swift @@ -32,6 +32,7 @@ public class VideoSink { private let isRealTime: Bool private let usesReplayBuffer: Bool private let replayBufferDuration: Int + private var isActive: Bool var accessingBookmarkURL = false @@ -59,6 +60,11 @@ public class VideoSink { self.audioReplayBuffer = ReplayBuffer(buffer: [], maxLengthInSeconds: Double(replayBufferDuration)) } self.isStopping = false + self.isActive = true + } + + public func makeActive() { + self.isActive = true } /// Appends a video frame to the destination movie file. @@ -97,52 +103,56 @@ public class VideoSink { } func initializeAssetWriters() throws { - //pretty ugly still logger.notice("Initializing file asset writers.") + let bookmarkedData = UserDefaults.standard.data(forKey: "mostRecentSinkURL") + var isStale = false do { - let bookmarkedData = UserDefaults.standard.data(forKey: "mostRecentSinkURL") - var isStale = false if bookmarkedData != nil { + //self.bookmarkedURL = try URL(resolvingBookmarkData: bookmarkedData!, bookmarkDataIsStale: &isStale) self.bookmarkedURL = try URL(resolvingBookmarkData: bookmarkedData!, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) - } - if bookmarkedURL?.path() == outputFolder.path() { - self.accessingBookmarkURL = true - bookmarkedURL?.startAccessingSecurityScopedResource() - } - let fileExtension = self.fileType == .mov ? "mov" : "mp4" - let sinkURL = outputFolder.appendingRecordFilename(fileExtension: fileExtension) - let bookmarkData = try outputFolder.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) - UserDefaults.standard.setValue(bookmarkData, forKey: "mostRecentSinkURL") - assetWriter = try AVAssetWriter(outputURL: sinkURL, fileType: fileType) - - let videoFormatDesc = try CMFormatDescription(videoCodecType: CMFormatDescription.MediaSubType(rawValue: codec), width: width, height: height) - - assetWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: nil, sourceFormatHint: videoFormatDesc) - let audioFormatDescription = AudioStreamBasicDescription(mSampleRate: 48000.0, mFormatID: kAudioFormatLinearPCM, mFormatFlags: 0x29, mBytesPerPacket: 4, mFramesPerPacket: 1, mBytesPerFrame: 4, mChannelsPerFrame: 2, mBitsPerChannel: 32, mReserved: 0) - let outputSettings = [ - AVFormatIDKey: UInt(kAudioFormatLinearPCM), - AVSampleRateKey: 48000, - AVNumberOfChannelsKey: 2, - //AVChannelLayoutKey: NSData(bytes:&channelLayout, length:MemoryLayout.size), - AVLinearPCMBitDepthKey: 16, - AVLinearPCMIsNonInterleaved: false, - AVLinearPCMIsFloatKey: false, - AVLinearPCMIsBigEndianKey: false - ] as [String : Any] - let cmFormat = try CMFormatDescription(audioStreamBasicDescription: audioFormatDescription) - assetWriterAudioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings, sourceFormatHint: cmFormat) - - - assetWriterInput.expectsMediaDataInRealTime = true - assetWriterAudioInput.expectsMediaDataInRealTime = true - - assetWriter.add(assetWriterInput) - assetWriter.add(assetWriterAudioInput) - guard assetWriter.startWriting() else { - throw assetWriter.error! + if bookmarkedURL?.path() == outputFolder.path() { + self.accessingBookmarkURL = true + bookmarkedURL?.startAccessingSecurityScopedResource() + } } } catch { - logger.fault("Critical error initializing asset writers: \(error, privacy: .public)") + logger.fault("Failed to create bookmark URL from serialized NSData with security scope: \(error, privacy: .public)") + + } + let fileExtension = self.fileType == .mov ? "mov" : "mp4" + let sinkURL = outputFolder.appendingRecordFilename(fileExtension: fileExtension) + let bookmarkData = try outputFolder.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) + UserDefaults.standard.setValue(bookmarkData, forKey: "mostRecentSinkURL") + assetWriter = try AVAssetWriter(outputURL: sinkURL, fileType: fileType) + + let videoFormatDesc = try CMFormatDescription(videoCodecType: CMFormatDescription.MediaSubType(rawValue: codec), width: width, height: height) + + assetWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: nil, sourceFormatHint: videoFormatDesc) + let audioFormatDescription = AudioStreamBasicDescription(mSampleRate: 48000.0, mFormatID: kAudioFormatLinearPCM, mFormatFlags: 0x29, mBytesPerPacket: 4, mFramesPerPacket: 1, mBytesPerFrame: 4, mChannelsPerFrame: 2, mBitsPerChannel: 32, mReserved: 0) + let outputSettings = [ + AVFormatIDKey: UInt(kAudioFormatLinearPCM), + AVSampleRateKey: 48000, + AVNumberOfChannelsKey: 2, + //AVChannelLayoutKey: NSData(bytes:&channelLayout, length:MemoryLayout.size), + AVLinearPCMBitDepthKey: 16, + AVLinearPCMIsNonInterleaved: false, + AVLinearPCMIsFloatKey: false, + AVLinearPCMIsBigEndianKey: false + ] as [String : Any] + let cmFormat = try CMFormatDescription(audioStreamBasicDescription: audioFormatDescription) + assetWriterAudioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings, sourceFormatHint: cmFormat) + + + assetWriterInput.expectsMediaDataInRealTime = true + assetWriterAudioInput.expectsMediaDataInRealTime = true + + assetWriter.add(assetWriterInput) + assetWriter.add(assetWriterAudioInput) + guard assetWriter.startWriting() else { + throw assetWriter.error! + } + /*catch { + logger.fault("Failed to create bookmark URL from serialized NSData with security scope: \(error, privacy: .public)") if self.accessingBookmarkURL { self.bookmarkedURL?.stopAccessingSecurityScopedResource() self.accessingBookmarkURL = false @@ -150,7 +160,7 @@ public class VideoSink { self.assetWriter?.cancelWriting() //this should be all the cleanup we need. everything else with `try` //shouldn't have any side effects, unlike AVAssetWriter and security-scoped bookmark - } + }*/ } public func sendAudioBuffer(_ buffer: CMSampleBuffer) { diff --git a/CaptureSample/Views/CapturePreview.swift b/CaptureSample/Views/CapturePreview.swift index 29a60f8..d695b21 100644 --- a/CaptureSample/Views/CapturePreview.swift +++ b/CaptureSample/Views/CapturePreview.swift @@ -59,6 +59,7 @@ struct CaptureSplitViewPreview: NSViewRepresentable { if let frame = frame.encodedFrame { self.renderer.enqueue(frame) } + //encodedContentLayer.contents = frame.encodedSurface } @@ -81,8 +82,6 @@ struct CaptureSplitViewPreview: NSViewRepresentable { self.isVertical = true self.addSubview(self.firstView) self.addSubview(self.secondView) - //let scale = CGFloat(IOSurfaceGetHeight(firstView.layer!.contents as! IOSurface)) / self.frame.height - //firstView.layer?.contentsScale = scale //secondView.layer?.contentsScale = scale self.wantsLayer = true if #available(macOS 14.0, *) { diff --git a/CaptureSample/Views/Configuration View/VideoCaptureConfigurationView.swift b/CaptureSample/Views/Configuration View/VideoCaptureConfigurationView.swift index 96f38a4..8ae80be 100644 --- a/CaptureSample/Views/Configuration View/VideoCaptureConfigurationView.swift +++ b/CaptureSample/Views/Configuration View/VideoCaptureConfigurationView.swift @@ -33,21 +33,28 @@ struct VideoCaptureConfigurationView: View { Text("Screen Content:") switch screenRecorder.captureType { case .display: - Picker("Display", selection: $screenRecorder.selectedDisplay) { - ForEach(screenRecorder.availableDisplays, id: \.self) { display in - Text(display.displayName) - .tag(SCDisplay?.some(display)) + VStack { + Picker("Display", selection: $screenRecorder.selectedDisplay) { + ForEach(screenRecorder.availableDisplays, id: \.self) { display in + Text(display.displayName) + .tag(SCDisplay?.some(display)) + } } - } - .onHover(perform: { hovering in - Task { - await self.screenRecorder.refreshAvailableContent() + .onHover(perform: { hovering in + Task { + await self.screenRecorder.refreshAvailableContent() + } + }) + .alignmentGuide(.imageTitleAlignmentGuide) { dimension in + dimension[.leading] } - }) - .alignmentGuide(.imageTitleAlignmentGuide) { dimension in - dimension[.leading] + .frame(width: 150) + + List(screenRecorder.availableApps, selection: $screenRecorder.selectedApplications) { + Text($0.applicationName) + } + .frame(height: 100) } - .frame(width: 150) case .window: Picker("Window", selection: $screenRecorder.selectedWindow) { diff --git a/Record.xcodeproj/project.pbxproj b/Record.xcodeproj/project.pbxproj index 74bca66..b5c4d3c 100644 --- a/Record.xcodeproj/project.pbxproj +++ b/Record.xcodeproj/project.pbxproj @@ -29,7 +29,6 @@ 0D368B212A6CF6BA00DA288D /* VideoSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D368B202A6CF6BA00DA288D /* VideoSink.swift */; }; 0D4875402A81A20800F6D832 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D48753F2A81A20800F6D832 /* Enums.swift */; }; 0D52D73F2AB9283F0091AD97 /* EventTap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D52D73E2AB9283F0091AD97 /* EventTap.swift */; }; - 0D5C3BB62ACE437800417FEB /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = 0D5C3BB52ACE437800417FEB /* KeyboardShortcuts */; }; 0D5C529C2AD60B8E00D11280 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D5C529B2AD60B8E00D11280 /* Logging.swift */; }; 0DE5C82F2A95EDC60054AC23 /* PickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE5C82E2A95EDC60054AC23 /* PickerView.swift */; }; C470F0812811C5CB00D29309 /* ScreenRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C470F0802811C5CB00D29309 /* ScreenRecorder.swift */; }; @@ -45,6 +44,19 @@ C4EB90D428108656006A595C /* ConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB90D328108656006A595C /* ConfigurationView.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + 0DBF97E42AEB29CF00D21A33 /* Embed System Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(SYSTEM_EXTENSIONS_FOLDER_PATH)"; + dstSubfolderSpec = 16; + files = ( + ); + name = "Embed System Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 0D1326772A85612B00B1E886 /* CVPixelBufferHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CVPixelBufferHelpers.swift; sourceTree = ""; }; 0D152E9E2AB9FF2700FEB9CE /* HotkeysPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeysPreferencesView.swift; sourceTree = ""; }; @@ -69,8 +81,9 @@ 0D48753F2A81A20800F6D832 /* Enums.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = ""; }; 0D52D73E2AB9283F0091AD97 /* EventTap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTap.swift; sourceTree = ""; }; 0D5C529B2AD60B8E00D11280 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + 0D64B0E62B1699E200A8CAFE /* Record-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Record-Info.plist"; sourceTree = ""; }; 0DE5C82E2A95EDC60054AC23 /* PickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerView.swift; sourceTree = ""; }; - 0DF11FDD2A6ECBA500B45306 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 0DF11FDD2A6ECBA500B45306 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.markdown; }; 7C6C99F1D4B6E3EBA3A7B7DF /* LICENSE.txt */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; C470F0802811C5CB00D29309 /* ScreenRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecorder.swift; sourceTree = ""; }; C471DFF92809F440001D24C9 /* PowerMeter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerMeter.swift; sourceTree = ""; }; @@ -92,7 +105,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 0D5C3BB62ACE437800417FEB /* KeyboardShortcuts in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -160,6 +172,7 @@ C4B0DA9E276BA4460015082A = { isa = PBXGroup; children = ( + 0D64B0E62B1699E200A8CAFE /* Record-Info.plist */, 0D164AAE2A996C6E003F2F7E /* objective-c-xcode.yml */, 0DF11FDD2A6ECBA500B45306 /* README.md */, C4B0DAA9276BA4460015082A /* CaptureSample */, @@ -226,6 +239,7 @@ C4B0DAA3276BA4460015082A /* Sources */, C4B0DAA4276BA4460015082A /* Frameworks */, C4B0DAA5276BA4460015082A /* Resources */, + 0DBF97E42AEB29CF00D21A33 /* Embed System Extensions */, ); buildRules = ( ); @@ -233,7 +247,6 @@ ); name = Record; packageProductDependencies = ( - 0D5C3BB52ACE437800417FEB /* KeyboardShortcuts */, ); productName = CaptureIt; productReference = C4B0DAA7276BA4460015082A /* Record.app */; @@ -246,7 +259,8 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1330; + DefaultBuildSystemTypeForWorkspace = Latest; + LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; ORGANIZATIONNAME = jcm; TargetAttributes = { @@ -266,7 +280,6 @@ ); mainGroup = C4B0DA9E276BA4460015082A; packageReferences = ( - 0D5C3BB42ACE437800417FEB /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */, ); productRefGroup = C4B0DAA8276BA4460015082A /* Products */; projectDirPath = ""; @@ -455,12 +468,12 @@ C4B0DAB7276BA4480015082A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = CaptureSample/CaptureSample.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; @@ -470,6 +483,7 @@ ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Record-Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Record; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( @@ -479,7 +493,7 @@ MACOSX_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0.1; OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.jcm.record; + PRODUCT_BUNDLE_IDENTIFIER = com.jcm.Record; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; @@ -491,12 +505,12 @@ C4B0DAB8276BA4480015082A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = CaptureSample/CaptureSample.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; @@ -506,6 +520,7 @@ ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Record-Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Record; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( @@ -515,7 +530,7 @@ MACOSX_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0.1; OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.jcm.record; + PRODUCT_BUNDLE_IDENTIFIER = com.jcm.Record; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; @@ -545,25 +560,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - 0D5C3BB42ACE437800417FEB /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/sindresorhus/KeyboardShortcuts"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.15.0; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 0D5C3BB52ACE437800417FEB /* KeyboardShortcuts */ = { - isa = XCSwiftPackageProductDependency; - package = 0D5C3BB42ACE437800417FEB /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */; - productName = KeyboardShortcuts; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = C4B0DA9F276BA4460015082A /* Project object */; }