Atoll/DynamicIsland/components/ScreenAssistant/ScreenshotSnippingTool_old.swift
2025-09-22 00:39:40 +05:30

500 lines
No EOL
17 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// This file has been replaced - keeping for reference only
// MARK: - Screenshot Snipping Tool
class ScreenshotSnippingTool: NSObject, ObservableObject {
static let shared = ScreenshotSnippingTool()
@Published var isSnipping = false
@Published var hasPermissions = false
@Published var permissionStatus: PermissionStatus = .unknown
private var snippingWindow: SnippingWindow?
enum PermissionStatus {
case unknown
case granted
case denied
case restricted
}
override init() {
super.init()
checkPermissionsAsync()
}
private func checkPermissionsAsync() {
Task {
await checkPermissions()
}
}
@MainActor
func checkPermissions() async {
print("🔍 ScreenshotTool: Checking permissions for bundle: \(Bundle.main.bundleIdentifier ?? "unknown")")
do {
// First try a simple content check to see if we can access ScreenCaptureKit
let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: false)
// If we get here without error, permissions are granted
self.hasPermissions = true
self.permissionStatus = .granted
print("✅ ScreenshotTool: Permissions granted for \(content.displays.count) displays")
} catch let error as NSError {
print("❌ ScreenshotTool: Permission error - Code: \(error.code), Domain: \(error.domain)")
// Handle specific error codes
switch error.code {
case -3801: // User declined TCC permission
self.hasPermissions = false
self.permissionStatus = .denied
print("❌ ScreenshotTool: Permission denied by user - TCC database entry missing or denied")
case -3802: // App is not authorized for screen capture
self.hasPermissions = false
self.permissionStatus = .restricted
print("❌ ScreenshotTool: App not authorized for screen capture - System restriction")
default:
self.hasPermissions = false
self.permissionStatus = .unknown
print("❌ ScreenshotTool: Unknown permission error - \(error.localizedDescription)")
}
// Provide user guidance
DispatchQueue.main.async {
self.showPermissionGuidance()
}
}
}
private func showPermissionGuidance() {
let alert = NSAlert()
alert.messageText = "Screen Recording Permission Required"
alert.informativeText = """
Dynamic Island needs screen recording permission to capture screenshots.
To enable this:
1. Open System Settings (or System Preferences)
2. Go to Privacy & Security → Screen Recording
3. Enable 'Dynamic Island'
4. Restart the app if needed
"""
alert.alertStyle = .warning
alert.addButton(withTitle: "Open System Settings")
alert.addButton(withTitle: "Cancel")
let response = alert.runModal()
if response == .alertFirstButtonReturn {
// Open System Settings to Screen Recording
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture") {
NSWorkspace.shared.open(url)
}
}
}
func requestPermissions() async {
// Only request if we don't already have them
guard !hasPermissions else { return }
await checkPermissions()
}
func startSnipping() {
guard !isSnipping else { return }
// Check permissions first
if !hasPermissions {
print("❌ ScreenshotTool: No permissions - checking status")
Task {
await requestPermissions()
}
return
}
print("🖼️ ScreenshotTool: Starting screen snipping")
isSnipping = true
// Hide all app windows temporarily
hideAppWindows()
// Create snipping overlay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.createSnippingOverlay()
}
}
private func hideAppWindows() {
for window in NSApp.windows {
window.orderOut(nil)
}
}
private func restoreAppWindows() {
for window in NSApp.windows {
if window != snippingWindow {
window.orderFront(nil)
}
}
}
private func createSnippingOverlay() {
guard let screen = NSScreen.main else {
print("❌ ScreenshotTool: No main screen available")
finishSnipping()
return
}
print("🎯 ScreenshotTool: Creating snipping overlay")
snippingWindow = SnippingWindow(screen: screen) { [weak self] rect in
guard let self = self else {
print("❌ ScreenshotTool: Self deallocated during area selection")
return
}
self.captureScreenArea(rect)
}
guard let window = snippingWindow else {
print("❌ ScreenshotTool: Failed to create snipping window")
finishSnipping()
return
}
// Just make it key and order front, don't try to make it main
window.makeKeyAndOrderFront(nil)
window.orderFrontRegardless()
// Activate the app to ensure proper focus
NSApp.activate(ignoringOtherApps: true)
print("✅ ScreenshotTool: Snipping overlay created and activated")
}
private func captureScreenArea(_ rect: CGRect) {
print("🖼️ ScreenshotTool: Capturing area: \(rect)")
Task {
do {
let screenshot = try await captureScreenshot(rect: rect)
DispatchQueue.main.async { [weak self] in
guard let self = self else {
print("❌ ScreenshotTool: Self deallocated during screenshot callback")
return
}
print("✅ ScreenshotTool: Screenshot saved successfully")
// Capture screenshot URL before cleanup
let screenshotURL = screenshot
// Finish snipping first to clean up window state
self.finishSnipping()
// Post notification after cleanup with captured URL
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
print("📸 ScreenshotTool: Posting screenshot notification")
NotificationCenter.default.post(
name: Notification.Name("screenshotCaptured"),
object: screenshotURL
)
print("✅ ScreenshotTool: Notification posted successfully")
}
}
} catch {
print("❌ ScreenshotTool: Failed to capture screenshot - \(error)")
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
// Show error to user
self.showScreenshotError(error)
self.finishSnipping()
}
}
}
}
private func showScreenshotError(_ error: Error) {
let alert = NSAlert()
alert.messageText = "Screenshot Failed"
alert.informativeText = "Failed to capture screenshot: \(error.localizedDescription)"
alert.alertStyle = .warning
alert.addButton(withTitle: "OK")
alert.runModal()
}
private func captureScreenshot(rect: CGRect) async throws -> URL {
// Use ScreenCaptureKit for modern screenshot capture
let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
guard let display = content.displays.first else {
throw ScreenshotError.noDisplayFound
}
let config = SCStreamConfiguration()
config.width = Int(rect.width)
config.height = Int(rect.height)
config.sourceRect = rect
config.pixelFormat = kCVPixelFormatType_32BGRA
config.showsCursor = false
let filter = SCContentFilter(display: display, excludingWindows: [])
let image = try await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config)
// Ensure screenshot directory exists
let screenshotDir = ScreenAssistantManager.screenshotDataDirectory
if !FileManager.default.fileExists(atPath: screenshotDir.path) {
try FileManager.default.createDirectory(at: screenshotDir, withIntermediateDirectories: true)
}
// Save to temporary file
let filename = "screenshot_\(Date().timeIntervalSince1970).png"
let screenshotURL = screenshotDir.appendingPathComponent(filename)
// Create NSImage from CGImage and convert to PNG
let nsImage = NSImage(cgImage: image, size: NSSize(width: image.width, height: image.height))
guard let imageData = nsImage.tiffRepresentation,
let bitmapRep = NSBitmapImageRep(data: imageData),
let pngData = bitmapRep.representation(using: NSBitmapImageRep.FileType.png, properties: [:]) else {
throw ScreenshotError.imageConversionFailed
}
do {
try pngData.write(to: screenshotURL)
print("✅ ScreenshotTool: Saved screenshot to: \(screenshotURL.path)")
return screenshotURL
} catch {
print("❌ ScreenshotTool: Failed to write screenshot file - \(error)")
throw ScreenshotError.fileWriteError
}
}
private func finishSnipping() {
guard isSnipping else {
print("⚠️ ScreenshotTool: finishSnipping called but not currently snipping")
return
}
print("🔄 ScreenshotTool: Finishing snipping process")
isSnipping = false
// Close and cleanup snipping window safely
if let window = snippingWindow {
window.orderOut(nil)
window.close()
snippingWindow = nil
print("🪟 ScreenshotTool: Snipping window closed and cleared")
}
// Restore app windows with delay to prevent conflicts
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.restoreAppWindows()
print("🏠 ScreenshotTool: App windows restored")
}
print("✅ ScreenshotTool: Snipping process completed")
}
func cancelSnipping() {
finishSnipping()
}
}
// MARK: - Snipping Window
class SnippingWindow: NSWindow {
private let onAreaSelected: (CGRect) -> Void
private var selectionOverlay: SelectionOverlayView?
init(screen: NSScreen, onAreaSelected: @escaping (CGRect) -> Void) {
self.onAreaSelected = onAreaSelected
super.init(
contentRect: screen.frame,
styleMask: [.borderless, .fullSizeContentView],
backing: .buffered,
defer: false
)
setupWindow(screen: screen)
setupContent()
}
override var canBecomeKey: Bool {
return true
}
override var canBecomeMain: Bool {
return true
}
private func setupWindow(screen: NSScreen) {
backgroundColor = NSColor.black.withAlphaComponent(0.3)
isOpaque = false
level = .screenSaver
ignoresMouseEvents = false
acceptsMouseMovedEvents = true
isMovableByWindowBackground = false
titleVisibility = .hidden
titlebarAppearsTransparent = true
collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
setFrame(screen.frame, display: true)
}
private func setupContent() {
selectionOverlay = SelectionOverlayView { [weak self] rect in
self?.onAreaSelected(rect)
}
if let overlay = selectionOverlay {
let hostingView = NSHostingView(rootView: overlay)
contentView = hostingView
}
}
override func keyDown(with event: NSEvent) {
if event.keyCode == 53 { // ESC key
close()
ScreenshotSnippingTool.shared.cancelSnipping()
} else {
super.keyDown(with: event)
}
}
}
// MARK: - Selection Overlay View
struct SelectionOverlayView: NSViewRepresentable {
let onAreaSelected: (CGRect) -> Void
func makeNSView(context: Context) -> SelectionView {
let view = SelectionView()
view.onAreaSelected = onAreaSelected
return view
}
func updateNSView(_ nsView: SelectionView, context: Context) {}
}
// MARK: - Selection View
class SelectionView: NSView {
var onAreaSelected: ((CGRect) -> Void)?
private var startPoint: CGPoint?
private var currentRect: CGRect = .zero
private var isDragging = false
override func awakeFromNib() {
super.awakeFromNib()
wantsLayer = true
layer?.backgroundColor = NSColor.clear.cgColor
}
override func mouseDown(with event: NSEvent) {
startPoint = convert(event.locationInWindow, from: nil)
isDragging = true
currentRect = .zero
needsDisplay = true
}
override func mouseDragged(with event: NSEvent) {
guard let start = startPoint, isDragging else { return }
let currentPoint = convert(event.locationInWindow, from: nil)
let x = min(start.x, currentPoint.x)
let y = min(start.y, currentPoint.y)
let width = abs(currentPoint.x - start.x)
let height = abs(currentPoint.y - start.y)
currentRect = CGRect(x: x, y: y, width: width, height: height)
needsDisplay = true
}
override func mouseUp(with event: NSEvent) {
guard isDragging, !currentRect.isEmpty else {
isDragging = false
return
}
isDragging = false
// Convert to screen coordinates
if let window = self.window {
let screenRect = window.convertToScreen(currentRect)
// Flip Y coordinate for screen capture
let flippedRect = CGRect(
x: screenRect.origin.x,
y: NSScreen.main!.frame.height - screenRect.origin.y - screenRect.height,
width: screenRect.width,
height: screenRect.height
)
onAreaSelected?(flippedRect)
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard isDragging, !currentRect.isEmpty else { return }
// Draw selection rectangle
NSColor.systemBlue.withAlphaComponent(0.3).setFill()
currentRect.fill()
NSColor.systemBlue.setStroke()
let path = NSBezierPath(rect: currentRect)
path.lineWidth = 2.0
path.stroke()
// Draw selection info
let infoText = String(format: "%.0f × %.0f", currentRect.width, currentRect.height)
let attributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 12),
.foregroundColor: NSColor.white,
.backgroundColor: NSColor.black.withAlphaComponent(0.8)
]
let attributedString = NSAttributedString(string: infoText, attributes: attributes)
let textRect = CGRect(
x: currentRect.maxX - 80,
y: currentRect.minY - 20,
width: 80,
height: 20
)
attributedString.draw(in: textRect)
}
}
// MARK: - Screenshot Error Types
enum ScreenshotError: Error, LocalizedError {
case noDisplayFound
case imageConversionFailed
case fileWriteError
var errorDescription: String? {
switch self {
case .noDisplayFound:
return "No display found for screenshot capture"
case .imageConversionFailed:
return "Failed to convert screenshot image"
case .fileWriteError:
return "Failed to save screenshot file"
}
}
}
// MARK: - ScreenAssistantManager Extension for Screenshot Directory
extension ScreenAssistantManager {
static var screenshotDataDirectory: URL {
let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let appDir = appSupport.appendingPathComponent(bundleIdentifier)
let screenshotDir = appDir.appendingPathComponent("Screenshots")
// Create directory if it doesn't exist
try? FileManager.default.createDirectory(at: screenshotDir, withIntermediateDirectories: true)
return screenshotDir
}
}