]> git.r.bdr.sh - rbdr/captura/commitdiff
Add multimonitor support 1.0.0
authorRuben Beltran del Rio <redacted>
Wed, 29 Nov 2023 10:25:12 +0000 (11:25 +0100)
committerRuben Beltran del Rio <redacted>
Wed, 29 Nov 2023 10:25:12 +0000 (11:25 +0100)
Captura.xcodeproj/project.pbxproj
Captura.xcodeproj/xcuserdata/rbdr.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
Captura/CapturaApp.swift
Captura/Core Extensions/CVImageBuffer+cgImage.swift
Captura/Domain/CapturaCaptureSession.swift
Captura/Presentation/Windows/RecordingWindow.swift

index 31e5fb642469c5e1d21bfc0f4c9ed089ccff1e7c..47da1f3351676ab5c32c816b9b6699850d01fc64 100644 (file)
                                CODE_SIGN_ENTITLEMENTS = Captura/Captura.entitlements;
                                CODE_SIGN_STYLE = Automatic;
                                COMBINE_HIDPI_IMAGES = YES;
                                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;
                                DEVELOPMENT_ASSET_PATHS = "\"Captura/Preview Content\"";
                                DEVELOPMENT_TEAM = S68NHQVJXW;
                                ENABLE_HARDENED_RUNTIME = YES;
                                CODE_SIGN_ENTITLEMENTS = Captura/Captura.entitlements;
                                CODE_SIGN_STYLE = Automatic;
                                COMBINE_HIDPI_IMAGES = YES;
                                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;
                                DEVELOPMENT_ASSET_PATHS = "\"Captura/Preview Content\"";
                                DEVELOPMENT_TEAM = S68NHQVJXW;
                                ENABLE_HARDENED_RUNTIME = YES;
index a710259bb60cd7f6f5194b25cac4e5ec61a6dcac..c1ed64469fde9211974604c2432e50641f8f1d57 100644 (file)
@@ -3,4 +3,70 @@
    uuid = "64A0C242-D356-455C-8377-13FD423E21EF"
    type = "1"
    version = "2.0">
    uuid = "64A0C242-D356-455C-8377-13FD423E21EF"
    type = "1"
    version = "2.0">
+   <Breakpoints>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            uuid = "BE57803D-825F-4BD3-A90A-66E06C9465B9"
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Captura/CapturaApp.swift"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "345"
+            endingLineNumber = "345"
+            landmarkName = "failed(_:)"
+            landmarkType = "7">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            uuid = "2F7C03C5-9042-41A2-B409-8E15E063FA4C"
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Captura/CapturaApp.swift"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "290"
+            endingLineNumber = "290"
+            landmarkName = "startRecording()"
+            landmarkType = "7">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            uuid = "A8CCA767-DD2D-482B-8F95-026C4A0BD9FA"
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Captura/Domain/CapturaCaptureSession.swift"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "42"
+            endingLineNumber = "42"
+            landmarkName = "startRecording()"
+            landmarkType = "7">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            uuid = "DE0C260E-5AA4-404B-B50E-83DAF27C9532"
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Captura/Domain/CapturaCaptureSession.swift"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "37"
+            endingLineNumber = "37"
+            landmarkName = "startRecording()"
+            landmarkType = "7">
+         </BreakpointContent>
+      </BreakpointProxy>
+   </Breakpoints>
 </Bucket>
 </Bucket>
index 67709a5df051374cad5322d3fe54b20fe7f7e972..00c5cc422f0e3728f24951a411c24c67ca1b9be5 100644 (file)
@@ -132,9 +132,14 @@ struct CapturaApp: App {
   
   func menuWillOpen(_ menu: NSMenu) {
     if captureState != .idle {
   
   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)
       if captureState == .recording {
         NotificationCenter.default.post(name: .stopRecording, object: nil, userInfo: nil)
+        return
       }
     }
   }
       }
     }
   }
index f8958f22eb5a89ab956abba7c784faf9560ec92f..3f59b8ec1006a023fed8dcdd31167672521e8ff7 100644 (file)
@@ -3,10 +3,19 @@ import ReplayKit
 
 extension CVImageBuffer {
   
 
 extension CVImageBuffer {
   
-  static let sharedContext = CIContext()
+  private static let contextQueue = DispatchQueue(label: "com.example.contextQueue")
+  static let sharedContext: CIContext = {
+          return CIContext()
+  }()
   
   var cgImage: CGImage? {
   
   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
   }
 }
   }
 }
index 80240e6a36d546b8618ede80808a072c7881d78f..89432822ce340547bad0c7774a00f5107502a426 100644 (file)
@@ -12,7 +12,11 @@ class CapturaCaptureSession: AVCaptureSession, AVCaptureFileOutputRecordingDeleg
     
     let displayId = screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as! CGDirectDisplayID
     let screenInput = AVCaptureScreenInput(displayID: displayId)
     
     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!)
     
     if self.canAddInput(screenInput!) {
       self.addInput(screenInput!)
index b5c44139cb6a26cac5b0d86c720379764a4c85af..7a64576acd161a97cfafbda235c0466c6edd073f 100644 (file)
@@ -33,16 +33,12 @@ class RecordingWindow: NSWindow {
     let recordingView = RecordingContentView(configuration, frame: boundingBox, button: button ?? NSZeroRect)
     recordingView.frame = boundingBox
     self.contentView = recordingView
     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
     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
   }
   
   // MARK: - Window Behavior Overrides
@@ -83,32 +79,23 @@ enum RecordingWindowState {
 class RecordingContentView: NSView {
   
   init(_ configuration: CaptureSessionConfiguration, frame: NSRect, button: NSRect) {
 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
     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(
     
     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")
   }
   
     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
   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
     
     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()
 
     if state == .idle && box == nil {
       let blackLine = NSBezierPath()
@@ -468,10 +457,6 @@ class RecordingContentView: NSView {
   }
   
   private func moveWindow() {
   }
   
   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 {
     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
         
         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()
           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)")
         }
       }
     }
         }
       }
     }