From: Ruben Beltran del Rio Date: Wed, 29 Nov 2023 10:25:12 +0000 (+0100) Subject: Add multimonitor support X-Git-Tag: 1.0.0 X-Git-Url: https://git.r.bdr.sh/rbdr/captura/commitdiff_plain/refs/heads/rbdr-multimonitor?ds=sidebyside Add multimonitor support --- diff --git a/Captura.xcodeproj/project.pbxproj b/Captura.xcodeproj/project.pbxproj index 31e5fb6..47da1f3 100644 --- a/Captura.xcodeproj/project.pbxproj +++ b/Captura.xcodeproj/project.pbxproj @@ -617,7 +617,7 @@ CODE_SIGN_ENTITLEMENTS = Captura/Captura.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"Captura/Preview Content\""; DEVELOPMENT_TEAM = S68NHQVJXW; ENABLE_HARDENED_RUNTIME = YES; @@ -648,7 +648,7 @@ CODE_SIGN_ENTITLEMENTS = Captura/Captura.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"Captura/Preview Content\""; DEVELOPMENT_TEAM = S68NHQVJXW; ENABLE_HARDENED_RUNTIME = YES; diff --git a/Captura.xcodeproj/xcuserdata/rbdr.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Captura.xcodeproj/xcuserdata/rbdr.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index a710259..c1ed644 100644 --- a/Captura.xcodeproj/xcuserdata/rbdr.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Captura.xcodeproj/xcuserdata/rbdr.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,4 +3,70 @@ uuid = "64A0C242-D356-455C-8377-13FD423E21EF" type = "1" version = "2.0"> + + + + + + + + + + + + + + + + + + diff --git a/Captura/CapturaApp.swift b/Captura/CapturaApp.swift index 67709a5..00c5cc4 100644 --- a/Captura/CapturaApp.swift +++ b/Captura/CapturaApp.swift @@ -132,9 +132,14 @@ struct CapturaApp: App { func menuWillOpen(_ menu: NSMenu) { if captureState != .idle { - menu.cancelTracking() + menu.cancelTrackingWithoutAnimation() + if captureState == .selectingArea { + NotificationCenter.default.post(name: .startRecording, object: nil, userInfo: nil) + return + } if captureState == .recording { NotificationCenter.default.post(name: .stopRecording, object: nil, userInfo: nil) + return } } } diff --git a/Captura/Core Extensions/CVImageBuffer+cgImage.swift b/Captura/Core Extensions/CVImageBuffer+cgImage.swift index f8958f2..3f59b8e 100644 --- a/Captura/Core Extensions/CVImageBuffer+cgImage.swift +++ b/Captura/Core Extensions/CVImageBuffer+cgImage.swift @@ -3,10 +3,19 @@ import ReplayKit extension CVImageBuffer { - static let sharedContext = CIContext() + private static let contextQueue = DispatchQueue(label: "com.example.contextQueue") + static let sharedContext: CIContext = { + return CIContext() + }() var cgImage: CGImage? { - let ciImage = CIImage(cvImageBuffer: self) - return CVImageBuffer.sharedContext.createCGImage(ciImage, from: CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(self), height: CVPixelBufferGetHeight(self))) + var result: CGImage? + CVImageBuffer.contextQueue.sync { + let ciImage = CIImage(cvImageBuffer: self) + let width = CVPixelBufferGetWidth(self) + let height = CVPixelBufferGetHeight(self) + result = CVImageBuffer.sharedContext.createCGImage(ciImage, from: CGRect(x: 0, y: 0, width: width, height: height)) + } + return result } } diff --git a/Captura/Domain/CapturaCaptureSession.swift b/Captura/Domain/CapturaCaptureSession.swift index 80240e6..8943282 100644 --- a/Captura/Domain/CapturaCaptureSession.swift +++ b/Captura/Domain/CapturaCaptureSession.swift @@ -12,7 +12,11 @@ class CapturaCaptureSession: AVCaptureSession, AVCaptureFileOutputRecordingDeleg let displayId = screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as! CGDirectDisplayID let screenInput = AVCaptureScreenInput(displayID: displayId) - screenInput?.cropRect = box.insetBy(dx: 1, dy: 1) + var croppingBox = NSOffsetRect(box, -screen.frame.origin.x, -screen.frame.origin.y) + if croppingBox.width.truncatingRemainder(dividingBy: 2) != 0 { + croppingBox.size.width -= 1 + } + screenInput?.cropRect = croppingBox.insetBy(dx: 1, dy: 1) if self.canAddInput(screenInput!) { self.addInput(screenInput!) diff --git a/Captura/Presentation/Windows/RecordingWindow.swift b/Captura/Presentation/Windows/RecordingWindow.swift index b5c4413..7a64576 100644 --- a/Captura/Presentation/Windows/RecordingWindow.swift +++ b/Captura/Presentation/Windows/RecordingWindow.swift @@ -33,16 +33,12 @@ class RecordingWindow: NSWindow { let recordingView = RecordingContentView(configuration, frame: boundingBox, button: button ?? NSZeroRect) recordingView.frame = boundingBox self.contentView = recordingView - //self.backgroundColor = NSColor(white: 1.0, alpha: 0.001) - self.backgroundColor = NSColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 0.5) + self.backgroundColor = NSColor(white: 1.0, alpha: 0.001) + // uncomment below to debug window placement visually + // self.backgroundColor = NSColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 0.5) self.level = .screenSaver self.isOpaque = false self.hasShadow = false - - print("AAAAH INIT CHANGE") - print("AAAAH FRAME X: \(recordingView.frame.minX) \(recordingView.frame.maxX) // Y: \(recordingView.frame.minY) \(recordingView.frame.maxY)") - print("AAAAH BOUNDS X: \(recordingView.bounds.minX) \(recordingView.bounds.maxX) // Y: \(recordingView.bounds.minY) \(recordingView.bounds.maxY)") - print("AAAAH WIN F X: \(self.frame.minX) \(self.frame.maxX) // Y: \(self.frame.minY) \(self.frame.maxY)") } // MARK: - Window Behavior Overrides @@ -83,32 +79,23 @@ enum RecordingWindowState { class RecordingContentView: NSView { init(_ configuration: CaptureSessionConfiguration, frame: NSRect, button: NSRect) { + self.buttonSize = button.size + var buttonOffset = NSPoint() + for screen in NSScreen.screens { + if screen.frame.intersects(button) { + let relativeY = screen.frame.height - (button.minY - screen.frame.minY) + let relativeX = screen.frame.width - (button.minX - screen.frame.minX) + buttonOffset = NSPoint(x: relativeX, y: relativeY) + } + } + self.buttonOffset = buttonOffset super.init(frame: frame) preventResize = configuration.preventResize preventMove = configuration.preventMove autoStart = configuration.autoStart - self.button = button - for screen in NSScreen.screens { - print(screen.frame) - // BEFORE YOU WENT TO BED: - // You were checking which screen contains the button, so you can calculate the offset which should give you - // the location of the button, which you can then use to draw the button at the same offset whenever you need - // to change screen! This would keep the behavior of pressing the record button. - // If this does work, remember to then test it with a Hi DPI display, because we might need to adjust for pixel - // density. - // Finally, if it does work, make sure the alternate monitor still responds, by adjusting the behavior of the - // real button - if screen.frame.intersects(button) { - print("CONTAINS! ->") - let relativeX = screen.frame.width - button.maxX - let relativeY = screen.frame.height - button.maxY - print("The rect is at (\(relativeX), \(relativeY)) relative to the top right of the screen frame.") - } else { - print("NO CONTAINS ->") - } - print(button) - } + self.bounds = frame + self.button = NSRect(x: frame.maxX - buttonOffset.x, y: frame.maxY - buttonOffset.y, width: buttonSize.width, height: buttonSize.height) if configuration.x != nil || configuration.y != nil || configuration.width != nil || configuration.height != nil { box = NSRect( @@ -130,7 +117,8 @@ class RecordingContentView: NSView { fatalError("init(coder:) has not been implemented") } - private var buttons: [NSRect] = [] + private let buttonSize: NSSize + private let buttonOffset: NSPoint public var button: NSRect? = nil @Published public var box: NSRect? = nil public var state: RecordingWindowState = .idle @@ -336,17 +324,18 @@ class RecordingContentView: NSView { let dashLength: CGFloat = 5.0 let lineWidth = 0.5 - - if let button { - let buttonPath = NSBezierPath() - buttonPath.move(to: NSPoint(x: button.minX, y: button.minY)) - buttonPath.line(to: NSPoint(x: button.maxX, y: button.minY)) - buttonPath.line(to: NSPoint(x: button.maxX, y: button.maxY)) - buttonPath.line(to: NSPoint(x: button.minX, y: button.maxY)) - buttonPath.line(to: NSPoint(x: button.minX, y: button.minY)) - NSColor(red: 1, green: 0, blue: 1, alpha: 1).setFill() - buttonPath.fill() - } + +// Uncomment below to debug button placement visually +// if let button { +// let buttonPath = NSBezierPath() +// buttonPath.move(to: NSPoint(x: button.minX, y: button.minY)) +// buttonPath.line(to: NSPoint(x: button.maxX, y: button.minY)) +// buttonPath.line(to: NSPoint(x: button.maxX, y: button.maxY)) +// buttonPath.line(to: NSPoint(x: button.minX, y: button.maxY)) +// buttonPath.line(to: NSPoint(x: button.minX, y: button.minY)) +// NSColor(red: 1, green: 0, blue: 1, alpha: 1).setFill() +// buttonPath.fill() +// } if state == .idle && box == nil { let blackLine = NSBezierPath() @@ -468,10 +457,6 @@ class RecordingContentView: NSView { } private func moveWindow() { - print("AAAAH BEFORE WE CHANGE") - print("AAAAH FRAME X: \(self.frame.minX) \(self.frame.maxX) // Y: \(self.frame.minY) \(self.frame.maxY)") - print("AAAAH BOUNDS X: \(self.bounds.minX) \(self.bounds.maxX) // Y: \(self.bounds.minY) \(self.bounds.maxY)") - print("AAAAH WIN F X: \(self.window?.frame.minX) \(self.window?.frame.maxX) // Y: \(self.window?.frame.minY) \(self.window?.frame.maxY)") let screen = NSScreen.screenWithMouse if let currentScreen = self.window?.screen { if currentScreen != screen { @@ -482,13 +467,10 @@ class RecordingContentView: NSView { if let window = self.window { self.bounds = frame + self.button = NSRect(x: frame.maxX - buttonOffset.x, y: frame.maxY - buttonOffset.y, width: buttonSize.width, height: buttonSize.height) window.setFrame(frame, display: true, animate: false) window.makeKeyAndOrderFront(nil) window.orderFrontRegardless() - print("AAAAH AFTER CHANGE") - print("AAAAH FRAME X: \(self.frame.minX) \(self.frame.maxX) // Y: \(self.frame.minY) \(self.frame.maxY)") - print("AAAAH BOUNDS X: \(self.bounds.minX) \(self.bounds.maxX) // Y: \(self.bounds.minY) \(self.bounds.maxY)") - print("AAAAH WIN F X: \(self.window?.frame.minX) \(self.window?.frame.maxX) // Y: \(self.window?.frame.minY) \(self.window?.frame.maxY)") } } }