From: Ruben Beltran del Rio Date: Tue, 19 Sep 2023 20:58:44 +0000 (+0200) Subject: Add multimonitor support X-Git-Tag: 1.0.0~2 X-Git-Url: https://git.r.bdr.sh/rbdr/captura/commitdiff_plain/7ee43fb83799abb89a69cfcd4e2146dd4eaa5045 Add multimonitor support --- diff --git a/Captura.xcodeproj/project.pbxproj b/Captura.xcodeproj/project.pbxproj index 4a11599..31e5fb6 100644 --- a/Captura.xcodeproj/project.pbxproj +++ b/Captura.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ B55DDFCC2A6F0253001A5E76 /* Notification+AppEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55DDFCB2A6F0253001A5E76 /* Notification+AppEvents.swift */; }; B55DDFCE2A6F069D001A5E76 /* RecordingWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55DDFCD2A6F069D001A5E76 /* RecordingWindow.swift */; }; B56C70CD2A6EFDF4009B97EB /* CaptureState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56C70CC2A6EFDF4009B97EB /* CaptureState.swift */; }; + B5E7B75F2AB5D84700D5F03B /* NSScreen+screenWithMouse.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E7B75E2AB5D84700D5F03B /* NSScreen+screenWithMouse.swift */; }; B5F915522A6EF80D007ECE8E /* CapturaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F915512A6EF80D007ECE8E /* CapturaApp.swift */; }; B5F915542A6EF80D007ECE8E /* PreferencesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F915532A6EF80D007ECE8E /* PreferencesScreen.swift */; }; B5F915562A6EF80E007ECE8E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5F915552A6EF80E007ECE8E /* Assets.xcassets */; }; @@ -85,6 +86,7 @@ B55DDFCB2A6F0253001A5E76 /* Notification+AppEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+AppEvents.swift"; sourceTree = ""; }; B55DDFCD2A6F069D001A5E76 /* RecordingWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingWindow.swift; sourceTree = ""; }; B56C70CC2A6EFDF4009B97EB /* CaptureState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaptureState.swift; sourceTree = ""; }; + B5E7B75E2AB5D84700D5F03B /* NSScreen+screenWithMouse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScreen+screenWithMouse.swift"; sourceTree = ""; }; B5F9154E2A6EF80D007ECE8E /* Captura.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Captura.app; sourceTree = BUILT_PRODUCTS_DIR; }; B5F915512A6EF80D007ECE8E /* CapturaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapturaApp.swift; sourceTree = ""; }; B5F915532A6EF80D007ECE8E /* PreferencesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesScreen.swift; sourceTree = ""; }; @@ -192,6 +194,7 @@ B55DDFCB2A6F0253001A5E76 /* Notification+AppEvents.swift */, B5278B272A739871009F6462 /* CGImage+resize.swift */, B5278B302A73AEAE009F6462 /* CVImageBuffer+cgImage.swift */, + B5E7B75E2AB5D84700D5F03B /* NSScreen+screenWithMouse.swift */, ); path = "Core Extensions"; sourceTree = ""; @@ -432,6 +435,7 @@ B5278B452A77D924009F6462 /* CaptureSessionConfiguration.swift in Sources */, B5278B2C2A739B3A009F6462 /* CapturaFile.swift in Sources */, B55403E72A79A08C004BCBAB /* CapturaShortcutsProvider.swift in Sources */, + B5E7B75F2AB5D84700D5F03B /* NSScreen+screenWithMouse.swift in Sources */, B5278B1F2A71BD9B009F6462 /* OutputSettings.swift in Sources */, B5278B2A2A73992D009F6462 /* GifRenderer.swift in Sources */, B5278B212A71BFC3009F6462 /* OutputFormatSetting.swift in Sources */, @@ -620,6 +624,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Captura/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; INFOPLIST_KEY_LSUIElement = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( @@ -627,7 +632,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = pizza.unlimited.Captura; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -650,6 +655,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Captura/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; INFOPLIST_KEY_LSUIElement = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( @@ -657,7 +663,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = pizza.unlimited.Captura; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Captura/Core Extensions/CVImageBuffer+cgImage.swift b/Captura/Core Extensions/CVImageBuffer+cgImage.swift index c30cb0c..f8958f2 100644 --- a/Captura/Core Extensions/CVImageBuffer+cgImage.swift +++ b/Captura/Core Extensions/CVImageBuffer+cgImage.swift @@ -3,9 +3,10 @@ import ReplayKit extension CVImageBuffer { + static let sharedContext = CIContext() + var cgImage: CGImage? { let ciImage = CIImage(cvImageBuffer: self) - let context = CIContext() - return context.createCGImage(ciImage, from: CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(self), height: CVPixelBufferGetHeight(self))) + return CVImageBuffer.sharedContext.createCGImage(ciImage, from: CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(self), height: CVPixelBufferGetHeight(self))) } } diff --git a/Captura/Core Extensions/NSScreen+screenWithMouse.swift b/Captura/Core Extensions/NSScreen+screenWithMouse.swift new file mode 100644 index 0000000..8c7f566 --- /dev/null +++ b/Captura/Core Extensions/NSScreen+screenWithMouse.swift @@ -0,0 +1,13 @@ +import Cocoa + +extension NSScreen { + static var screenWithMouse: NSScreen? { + let mouseLocation = NSEvent.mouseLocation + for screen in NSScreen.screens { + if NSMouseInRect(mouseLocation, screen.frame, false) { + return screen + } + } + return NSScreen.main + } +} diff --git a/Captura/Data/CapturaFile.swift b/Captura/Data/CapturaFile.swift index da1f2fb..4af71dc 100644 --- a/Captura/Data/CapturaFile.swift +++ b/Captura/Data/CapturaFile.swift @@ -27,5 +27,8 @@ struct CapturaFile { self.name = "Captura \(dateString)" self.baseDirectory = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask).first! + try? FileManager.default.createDirectory(at: self.baseDirectory.appendingPathComponent(appDirectory), + withIntermediateDirectories: true, + attributes: nil) } } diff --git a/Captura/Presentation/Windows/RecordingWindow.swift b/Captura/Presentation/Windows/RecordingWindow.swift index f5941dc..370a47a 100644 --- a/Captura/Presentation/Windows/RecordingWindow.swift +++ b/Captura/Presentation/Windows/RecordingWindow.swift @@ -13,11 +13,7 @@ class RecordingWindow: NSWindow { init(_ configuration: CaptureSessionConfiguration, _ button: NSRect?) { - let screens = NSScreen.screens - var boundingBox = NSZeroRect - for screen in screens { - boundingBox = NSUnionRect(boundingBox, screen.frame) - } + let boundingBox = NSScreen.screenWithMouse?.frame ?? NSZeroRect super.init( contentRect: boundingBox, @@ -25,6 +21,7 @@ class RecordingWindow: NSWindow { backing: .buffered, defer: false) + self.isReleasedWhenClosed = false self.collectionBehavior = [.canJoinAllSpaces] self.isMovableByWindowBackground = false @@ -37,10 +34,16 @@ class RecordingWindow: NSWindow { recordingView.frame = boundingBox recordingView.button = button self.contentView = recordingView - self.backgroundColor = NSColor(white: 1.0, alpha: 0.001) + //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.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 @@ -167,6 +170,12 @@ class RecordingContentView: NSView { self.addTrackingArea(trackingArea) } + override func mouseExited(with event: NSEvent) { + if state == .idle && box == nil { + self.moveWindow() + } + } + override func mouseMoved(with event: NSEvent) { self.mouseLocation = self.convert(event.locationInWindow, from: nil) @@ -305,6 +314,17 @@ 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() + } if state == .idle && box == nil { let blackLine = NSBezierPath() @@ -424,4 +444,31 @@ class RecordingContentView: NSView { text.draw(in: textRect, withAttributes: textAttributes) } + + 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 frame = screen?.frame ?? NSZeroRect + self.frame = frame + self.bounds = frame + self.updateTrackingAreas() + + if let window = self.window { + self.bounds = frame + 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)") + } + } + } + } }