diff --git a/Scream.xcodeproj/project.pbxproj b/Scream.xcodeproj/project.pbxproj index 84b59df..1b33bab 100644 --- a/Scream.xcodeproj/project.pbxproj +++ b/Scream.xcodeproj/project.pbxproj @@ -3,9 +3,21 @@ archiveVersion = 1; classes = { }; - objectVersion = 77; + objectVersion = 63; objects = { +/* Begin PBXBuildFile section */ + A98E8BF02F05B2A0006D4458 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98E8BEB2F05B2A0006D4458 /* AppDelegate.swift */; }; + A98E8BF12F05B2A0006D4458 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A98E8BEC2F05B2A0006D4458 /* Assets.xcassets */; }; + A98E8BF22F05B2A0006D4458 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = A98E8BEE2F05B2A0006D4458 /* MainMenu.xib */; }; + A98E8BF52F05B2A2006D4458 /* ScreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98E8BF32F05B2A2006D4458 /* ScreamTests.swift */; }; + A98E8BF92F05B2A5006D4458 /* ScreamUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98E8BF62F05B2A5006D4458 /* ScreamUITests.swift */; }; + A98E8BFA2F05B2A5006D4458 /* ScreamUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98E8BF72F05B2A5006D4458 /* ScreamUITestsLaunchTests.swift */; }; + A98E8BFD2F05D28D006D4458 /* ScreenRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98E8BFC2F05D28D006D4458 /* ScreenRecorder.swift */; }; + A98E8BFF2F06F46C006D4458 /* SCStreamOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98E8BFE2F06F46C006D4458 /* SCStreamOutput.swift */; }; + A98E8C012F06F496006D4458 /* StreamDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98E8C002F06F496006D4458 /* StreamDelegate.swift */; }; +/* End PBXBuildFile section */ + /* Begin PBXContainerItemProxy section */ A98E8BCF2F05B26D006D4458 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -27,26 +39,18 @@ A98E8BC02F05B26B006D4458 /* Scream.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Scream.app; sourceTree = BUILT_PRODUCTS_DIR; }; A98E8BCE2F05B26D006D4458 /* ScreamTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ScreamTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A98E8BD82F05B26D006D4458 /* ScreamUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ScreamUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A98E8BEB2F05B2A0006D4458 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + A98E8BEC2F05B2A0006D4458 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + A98E8BED2F05B2A0006D4458 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + A98E8BF32F05B2A2006D4458 /* ScreamTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreamTests.swift; sourceTree = ""; }; + A98E8BF62F05B2A5006D4458 /* ScreamUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreamUITests.swift; sourceTree = ""; }; + A98E8BF72F05B2A5006D4458 /* ScreamUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreamUITestsLaunchTests.swift; sourceTree = ""; }; + A98E8BFB2F05C7B6006D4458 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + A98E8BFC2F05D28D006D4458 /* ScreenRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecorder.swift; sourceTree = ""; }; + A98E8BFE2F06F46C006D4458 /* SCStreamOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SCStreamOutput.swift; sourceTree = ""; }; + A98E8C002F06F496006D4458 /* StreamDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamDelegate.swift; sourceTree = ""; }; /* End PBXFileReference section */ -/* Begin PBXFileSystemSynchronizedRootGroup section */ - A98E8BC22F05B26B006D4458 /* Scream */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = Scream; - sourceTree = ""; - }; - A98E8BD12F05B26D006D4458 /* ScreamTests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = ScreamTests; - sourceTree = ""; - }; - A98E8BDB2F05B26D006D4458 /* ScreamUITests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = ScreamUITests; - sourceTree = ""; - }; -/* End PBXFileSystemSynchronizedRootGroup section */ - /* Begin PBXFrameworksBuildPhase section */ A98E8BBD2F05B26B006D4458 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -75,9 +79,9 @@ A98E8BB72F05B26B006D4458 = { isa = PBXGroup; children = ( - A98E8BC22F05B26B006D4458 /* Scream */, - A98E8BD12F05B26D006D4458 /* ScreamTests */, - A98E8BDB2F05B26D006D4458 /* ScreamUITests */, + A98E8BEF2F05B2A0006D4458 /* Scream */, + A98E8BF42F05B2A2006D4458 /* ScreamTests */, + A98E8BF82F05B2A5006D4458 /* ScreamUITests */, A98E8BC12F05B26B006D4458 /* Products */, ); sourceTree = ""; @@ -92,6 +96,37 @@ name = Products; sourceTree = ""; }; + A98E8BEF2F05B2A0006D4458 /* Scream */ = { + isa = PBXGroup; + children = ( + A98E8BFB2F05C7B6006D4458 /* Info.plist */, + A98E8BEB2F05B2A0006D4458 /* AppDelegate.swift */, + A98E8BEC2F05B2A0006D4458 /* Assets.xcassets */, + A98E8BEE2F05B2A0006D4458 /* MainMenu.xib */, + A98E8BFC2F05D28D006D4458 /* ScreenRecorder.swift */, + A98E8C002F06F496006D4458 /* StreamDelegate.swift */, + A98E8BFE2F06F46C006D4458 /* SCStreamOutput.swift */, + ); + path = Scream; + sourceTree = ""; + }; + A98E8BF42F05B2A2006D4458 /* ScreamTests */ = { + isa = PBXGroup; + children = ( + A98E8BF32F05B2A2006D4458 /* ScreamTests.swift */, + ); + path = ScreamTests; + sourceTree = ""; + }; + A98E8BF82F05B2A5006D4458 /* ScreamUITests */ = { + isa = PBXGroup; + children = ( + A98E8BF62F05B2A5006D4458 /* ScreamUITests.swift */, + A98E8BF72F05B2A5006D4458 /* ScreamUITestsLaunchTests.swift */, + ); + path = ScreamUITests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -107,9 +142,6 @@ ); dependencies = ( ); - fileSystemSynchronizedGroups = ( - A98E8BC22F05B26B006D4458 /* Scream */, - ); name = Scream; packageProductDependencies = ( ); @@ -130,9 +162,6 @@ dependencies = ( A98E8BD02F05B26D006D4458 /* PBXTargetDependency */, ); - fileSystemSynchronizedGroups = ( - A98E8BD12F05B26D006D4458 /* ScreamTests */, - ); name = ScreamTests; packageProductDependencies = ( ); @@ -153,9 +182,6 @@ dependencies = ( A98E8BDA2F05B26D006D4458 /* PBXTargetDependency */, ); - fileSystemSynchronizedGroups = ( - A98E8BDB2F05B26D006D4458 /* ScreamUITests */, - ); name = ScreamUITests; packageProductDependencies = ( ); @@ -187,6 +213,7 @@ }; }; buildConfigurationList = A98E8BBB2F05B26B006D4458 /* Build configuration list for PBXProject "Scream" */; + compatibilityVersion = "Xcode 12.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -195,7 +222,6 @@ ); mainGroup = A98E8BB72F05B26B006D4458; minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; productRefGroup = A98E8BC12F05B26B006D4458 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -212,6 +238,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A98E8BF12F05B2A0006D4458 /* Assets.xcassets in Resources */, + A98E8BF22F05B2A0006D4458 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -236,6 +264,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A98E8BF02F05B2A0006D4458 /* AppDelegate.swift in Sources */, + A98E8BFF2F06F46C006D4458 /* SCStreamOutput.swift in Sources */, + A98E8C012F06F496006D4458 /* StreamDelegate.swift in Sources */, + A98E8BFD2F05D28D006D4458 /* ScreenRecorder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -243,6 +275,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A98E8BF52F05B2A2006D4458 /* ScreamTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -250,6 +283,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A98E8BF92F05B2A5006D4458 /* ScreamUITests.swift in Sources */, + A98E8BFA2F05B2A5006D4458 /* ScreamUITestsLaunchTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -268,6 +303,17 @@ }; /* End PBXTargetDependency section */ +/* Begin PBXVariantGroup section */ + A98E8BEE2F05B2A0006D4458 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + A98E8BED2F05B2A0006D4458 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ A98E8BE02F05B26D006D4458 /* Debug */ = { isa = XCBuildConfiguration; @@ -400,8 +446,19 @@ DEVELOPMENT_TEAM = 8JGND254B7; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Scream/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSMainNibFile = MainMenu; INFOPLIST_KEY_NSPrincipalClass = NSApplication; @@ -433,8 +490,19 @@ DEVELOPMENT_TEAM = 8JGND254B7; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Scream/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSMainNibFile = MainMenu; INFOPLIST_KEY_NSPrincipalClass = NSApplication; diff --git a/Scream/AppDelegate.swift b/Scream/AppDelegate.swift index 3018e7a..4c35482 100644 --- a/Scream/AppDelegate.swift +++ b/Scream/AppDelegate.swift @@ -9,10 +9,16 @@ import Cocoa @main class AppDelegate: NSObject, NSApplicationDelegate { + let sr = ScreenRecorder() @IBOutlet var window: NSWindow! - + @IBAction func Button(_ sender: Any) { + Task { + await sr.start() + } + } + func applicationDidFinishLaunching(_ aNotification: Notification) { // Insert code here to initialize your application } diff --git a/Scream/Base.lproj/MainMenu.xib b/Scream/Base.lproj/MainMenu.xib index 6958f4c..af44593 100644 --- a/Scream/Base.lproj/MainMenu.xib +++ b/Scream/Base.lproj/MainMenu.xib @@ -1,7 +1,7 @@ - + - + @@ -11,8 +11,8 @@ - - + + @@ -684,10 +684,23 @@ - + + + + diff --git a/Scream/Info.plist b/Scream/Info.plist new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/Scream/Info.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/Scream/SCStreamOutput.swift b/Scream/SCStreamOutput.swift new file mode 100644 index 0000000..44afd23 --- /dev/null +++ b/Scream/SCStreamOutput.swift @@ -0,0 +1,31 @@ +// +// SCStreamOutput.swift +// Scream +// +// Created by neon443 on 01/01/2026. +// + +import Foundation +import ScreenCaptureKit + +class StreamOutputDelegate: NSObject, SCStreamOutput { + func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) { + guard sampleBuffer.isValid else { return } + + switch type { + case .screen: + print("got a screen buffer") + guard let attachmentsArr = CMSampleBufferGetSampleAttachmentsArray( + sampleBuffer, + createIfNecessary: false + ) as? [[SCStreamFrameInfo: Any]], + let attachments = attachmentsArr.first else { return } + case .audio: + print("got an audio buffer") + case .microphone: + print("got a mic buffer") + @unknown default: + fatalError("wtf is ur stream sample type") + } + } +} diff --git a/Scream/ScreenRecorder.swift b/Scream/ScreenRecorder.swift new file mode 100644 index 0000000..bdb4f4b --- /dev/null +++ b/Scream/ScreenRecorder.swift @@ -0,0 +1,83 @@ +// +// ScreenRecorder.swift +// Scream +// +// Created by neon443 on 31/12/2025. +// + +import Foundation +import ScreenCaptureKit + +class ScreenRecorder: NSObject { + var isRunning: Bool = false + var isAppExluded: Bool = false + var isAudioEnabled: Bool = true + var filter: SCContentFilter? + var streamConfig = SCStreamConfiguration() + var stream: SCStream? + var streamDelegate = StreamDelegate() + var streamOutput = StreamOutputDelegate() + + let videoSampleBufferQueue = DispatchQueue(label: "videoSampleBufferQueue") + let audioSampleBufferQueue = DispatchQueue(label: "audioSampleBufferQueue") + + var canRecord: Bool { + true + } + + func start() async { + guard !isRunning else { return } + + let availableContent: SCShareableContent + do { + availableContent = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true) + } catch { + print(error.localizedDescription) + return + } + + var excludedApps = [SCRunningApplication]() + //if users exclude Scream from the screen share + //exclude by matching bundleid + if isAppExluded { + excludedApps = availableContent.applications.filter { app in + Bundle.main.bundleIdentifier == app.bundleIdentifier + } + } + filter = SCContentFilter(display: availableContent.displays.first!, excludingApplications: excludedApps, exceptingWindows: []) + + //TODO: hdr + + streamConfig.capturesAudio = isAudioEnabled + streamConfig.excludesCurrentProcessAudio = true +// streamConfig.captureMicrophone = true + + streamConfig.width = Int(NSScreen.main?.frame.width ?? 100) + streamConfig.height = Int(NSScreen.main?.frame.height ?? 100) + + streamConfig.minimumFrameInterval = CMTime(value: 1, timescale: 20) + streamConfig.queueDepth = 5 + + stream = SCStream(filter: filter!, configuration: streamConfig, delegate: streamDelegate) + + try! stream?.addStreamOutput(streamOutput, type: .screen, sampleHandlerQueue: videoSampleBufferQueue) + try! stream?.addStreamOutput(streamOutput, type: .audio, sampleHandlerQueue: audioSampleBufferQueue) +// try! stream?.addStreamOutput(streamOutput, type: .microphone, sampleHandlerQueue: videoSampleBufferQueue) + + //update the config using stream.updateConfiguration or .updateContentFilter + } +} + +extension ScreenRecorder: SCContentSharingPickerObserver { + func contentSharingPicker(_ picker: SCContentSharingPicker, didCancelFor stream: SCStream?) { + print("canceleed picker") + } + + func contentSharingPicker(_ picker: SCContentSharingPicker, didUpdateWith filter: SCContentFilter, for stream: SCStream?) { + print(picker.description) + } + + func contentSharingPickerStartDidFailWithError(_ error: any Error) { + print(error.localizedDescription) + } +} diff --git a/Scream/StreamDelegate.swift b/Scream/StreamDelegate.swift new file mode 100644 index 0000000..b9111bb --- /dev/null +++ b/Scream/StreamDelegate.swift @@ -0,0 +1,31 @@ +// +// StreamDelegate.swift +// Scream +// +// Created by neon443 on 01/01/2026. +// + +import Foundation +import ScreenCaptureKit + +class StreamDelegate: NSObject, SCStreamDelegate { + func outputVideoEffectDidStart(for stream: SCStream) { + print("presenter overlay started") + } + + func outputVideoEffectDidStop(for stream: SCStream) { + print("presenter overlay stopped") + } + + func streamDidBecomeActive(_ stream: SCStream) { + print("stream became Active") + } + + func streamDidBecomeInactive(_ stream: SCStream) { + print("stream became Inactive") + } + + func stream(_ stream: SCStream, didStopWithError error: any Error) { + print(error.localizedDescription) + } +}