Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 69 additions & 10 deletions electron/native/ScreenCaptureKitRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
private var outputURL: URL?
private var microphoneOutputURL: URL?
private var trackedWindowId: UInt32?
private var trackedWindowFrame: CGRect?
private var updatesSourceRectForTrackedWindow = false
private var windowValidationTask: Task<Void, Never>?
private var streamConfig: SCStreamConfiguration?
private var inlineAudioInput: AVAssetWriterInput?
private var firstInlineAudioSampleTime: CMTime?
private var capturesSystemAudio = false
Expand All @@ -68,6 +71,9 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
let config = try JSONDecoder().decode(CaptureConfig.self, from: data)
let availableContent = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
let streamConfig = SCStreamConfiguration()
self.streamConfig = streamConfig
trackedWindowFrame = nil
updatesSourceRectForTrackedWindow = false
capturesSystemAudio = config.capturesSystemAudio ?? false
capturesMicrophone = config.capturesMicrophone ?? false
if capturesMicrophone && !supportsNativeMicrophoneCapture(streamConfig: streamConfig) {
Expand Down Expand Up @@ -104,16 +110,22 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
throw NSError(domain: "RecordlyCapture", code: 3, userInfo: [NSLocalizedDescriptionKey: "Window not found"])
}

filter = SCContentFilter(desktopIndependentWindow: window)

let candidateDisplay = availableContent.displays.first(where: {
$0.frame.intersects(window.frame) || $0.frame.contains(CGPoint(x: window.frame.midX, y: window.frame.midY))
})
let candidateDisplay = Self.displayForWindowFrame(window.frame, displays: availableContent.displays)
let scaleFactor = ScreenCaptureRecorder.scaleFactor(for: candidateDisplay?.displayID ?? CGMainDisplayID())
outputWidth = max(2, Int(window.frame.width) * scaleFactor)
outputHeight = max(2, Int(window.frame.height) * scaleFactor)
if #available(macOS 14.0, *) {
streamConfig.ignoreShadowsSingleWindow = true

if let candidateDisplay, let owningApplication = window.owningApplication {
filter = SCContentFilter(display: candidateDisplay, including: [owningApplication], exceptingWindows: [])
streamConfig.sourceRect = window.frame
trackedWindowFrame = window.frame
updatesSourceRectForTrackedWindow = true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
filter = SCContentFilter(desktopIndependentWindow: window)
enableChildWindowCaptureIfSupported(streamConfig: streamConfig)
if #available(macOS 14.0, *) {
streamConfig.ignoreShadowsSingleWindow = true
}
}
streamConfig.width = outputWidth
streamConfig.height = outputHeight
Expand Down Expand Up @@ -359,6 +371,8 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
windowValidationTask?.cancel()
windowValidationTask = nil
trackedWindowId = nil
trackedWindowFrame = nil
updatesSourceRectForTrackedWindow = false

if let activeStream = stream {
do {
Expand All @@ -368,6 +382,7 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
}
}
stream = nil
streamConfig = nil
isRecording = false

if let originalBuffer = lastSampleBuffer, let videoInput = videoInput {
Expand Down Expand Up @@ -550,10 +565,18 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
return supportsConfigSelector && supportsDeviceSelector && supportsOutputType
}

private func enableChildWindowCaptureIfSupported(streamConfig: SCStreamConfiguration) {
guard streamConfig.responds(to: Selector(("setIncludeChildWindows:"))) else { return }

streamConfig.setValue(true, forKey: "includeChildWindows")
}

private func startWindowValidationIfNeeded() {
guard let trackedWindowId else {
windowValidationTask?.cancel()
windowValidationTask = nil
trackedWindowFrame = nil
updatesSourceRectForTrackedWindow = false
return
}

Expand All @@ -567,22 +590,59 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {

do {
let availableContent = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
let windowStillAvailable = availableContent.windows.contains(where: { $0.windowID == trackedWindowId })
if !windowStillAvailable {
guard let window = availableContent.windows.first(where: { $0.windowID == trackedWindowId }) else {
print("WINDOW_UNAVAILABLE")
fflush(stdout)
let outputPath = try await self.finishCapture()
print("Recording stopped. Output path: \(outputPath)")
fflush(stdout)
exit(0)
}

await self.updateTrackedWindowSourceRectIfNeeded(to: window.frame)
} catch {
continue
}
}
}
}

private func updateTrackedWindowSourceRectIfNeeded(to windowFrame: CGRect) async {
guard updatesSourceRectForTrackedWindow,
trackedWindowFrame != windowFrame,
let stream,
let streamConfig else {
return
}

streamConfig.sourceRect = windowFrame

do {
try await stream.updateConfiguration(streamConfig)
trackedWindowFrame = windowFrame
} catch {
fputs("Error updating capture source rect: \(error.localizedDescription)\n", stderr)
fflush(stderr)
}
}

private static func displayForWindowFrame(_ windowFrame: CGRect, displays: [SCDisplay]) -> SCDisplay? {
let windowCenter = CGPoint(x: windowFrame.midX, y: windowFrame.midY)
if let centeredDisplay = displays.first(where: { $0.frame.contains(windowCenter) }) {
return centeredDisplay
}

return displays
.compactMap { display -> (display: SCDisplay, intersectionArea: CGFloat)? in
let intersection = display.frame.intersection(windowFrame)
let intersectionArea = intersection.width * intersection.height
guard intersectionArea > 0 else { return nil }
return (display, intersectionArea)
}
.max { $0.intersectionArea < $1.intersectionArea }?
.display
}

private static func scaleFactor(for displayId: CGDirectDisplayID) -> Int {
guard let mode = CGDisplayCopyDisplayMode(displayId) else {
return 1
Expand Down Expand Up @@ -715,4 +775,3 @@ DispatchQueue.global(qos: .utility).async {
}

service.waitUntilFinished()

Binary file modified electron/native/bin/darwin-arm64/recordly-screencapturekit-helper
Binary file not shown.
Binary file modified electron/native/bin/darwin-x64/recordly-screencapturekit-helper
Binary file not shown.