Files
shotscreen/ShotScreen/Sources/EventCapture.swift
Nick Roodenrijs 0dabed11d2 🎉 ShotScreen v1.0 - Initial Release
🚀 First official release of ShotScreen with complete feature set:

 Core Features:
- Advanced screenshot capture system
- Multi-monitor support
- Professional UI/UX design
- Automated update system with Sparkle
- Apple notarized & code signed

🛠 Technical Excellence:
- Native Swift macOS application
- Professional build & deployment pipeline
- Comprehensive error handling
- Memory optimized performance

📦 Distribution Ready:
- Professional DMG packaging
- Apple notarization complete
- No security warnings for users
- Ready for public distribution

This is the foundation release that establishes ShotScreen as a premium screenshot tool for macOS.
2025-06-28 16:15:15 +02:00

280 lines
11 KiB
Swift

import AppKit
// MARK: - Event Capture Window
class EventCaptureWindow: NSWindow {
override var canBecomeKey: Bool {
return true
}
override var canBecomeMain: Bool {
return true
}
override func sendEvent(_ event: NSEvent) {
// During screenshot selection, we want to capture ALL events
// and prevent them from reaching other windows
switch event.type {
case .leftMouseDown, .leftMouseUp, .leftMouseDragged,
.rightMouseDown, .rightMouseUp, .rightMouseDragged,
.otherMouseDown, .otherMouseUp, .otherMouseDragged,
.mouseMoved, .mouseEntered, .mouseExited,
.scrollWheel, .keyDown, .keyUp:
// Process the event normally through our view hierarchy
super.sendEvent(event)
// DO NOT pass the event to other windows - this prevents
// other windows from being dragged or interacted with
return
default:
// For other event types, allow normal processing
super.sendEvent(event)
}
}
}
// MARK: - Event Capture View
class EventCaptureView: NSView {
weak var screenshotApp: ScreenshotApp?
var shouldDisplayAllScreensActiveText: Bool = false // Nieuwe property
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
// This view will handle mouse events - no special setup needed
self.wantsLayer = true // Zorg ervoor dat de view een layer heeft voor efficiënt tekenen
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect) // Belangrijk voor standaard teken gedrag
if shouldDisplayAllScreensActiveText {
// Teken "Alle Schermen Actief" tekst-overlay bovenaan het scherm van deze view
let instructionText = "All Screens Mode, Click to Capture, ESC to Cancel"
let attributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 18, weight: .medium),
.foregroundColor: NSColor.white.withAlphaComponent(0.9),
.strokeColor: NSColor.black.withAlphaComponent(0.5), // Tekst outline
.strokeWidth: -2.0, // Negatief voor outline binnen de letters
.paragraphStyle: {
let style = NSMutableParagraphStyle()
style.alignment = .center
return style
}()
]
let attributedString = NSAttributedString(string: instructionText, attributes: attributes)
let textSize = attributedString.size()
// self.bounds is de grootte van deze EventCaptureView, die het hele scherm beslaat.
let viewBounds = self.bounds
let textRect = NSRect(x: (viewBounds.width - textSize.width) / 2,
y: viewBounds.height - textSize.height - 30, // 30px van de top van het scherm
width: textSize.width,
height: textSize.height)
let backgroundPadding: CGFloat = 10
let backgroundRect = NSRect(
x: textRect.origin.x - backgroundPadding,
y: textRect.origin.y - backgroundPadding,
width: textRect.width + (2 * backgroundPadding),
height: textRect.height + (2 * backgroundPadding)
)
let BORDER_RADIUS: CGFloat = 10
let textBackgroundPath = NSBezierPath(roundedRect: backgroundRect, xRadius: BORDER_RADIUS, yRadius: BORDER_RADIUS)
NSColor.black.withAlphaComponent(0.4).setFill()
textBackgroundPath.fill()
attributedString.draw(in: textRect)
// NSLog("🎨 EventCaptureView: Drew 'Alle Schermen Actief' text.")
}
}
override func mouseDown(with event: NSEvent) {
guard let app = screenshotApp else {
// Even if app is nil, consume the event to prevent it from reaching other windows
return
}
if app.isMultiMonitorSelectionActive && !app.isDragging {
let globalLocation = NSEvent.mouseLocation
print("🎯 Event capture - starting selection at: \(globalLocation)")
// SET MOUSE TRACKING VARIABLES FOR SINGLE CLICK DETECTION
let allScreenModifier: UInt = (1 << 0) // Command key
app.isAllScreenModifierPressed = app.isModifierPressed(event.modifierFlags, modifier: allScreenModifier)
app.mouseDownLocation = globalLocation
app.mouseDownTime = CACurrentMediaTime()
app.hasMouseMoved = false
// Hide crosshairs during drag to prevent stuck crosshair issue
hideCrosshairs()
app.startDragSelection(at: globalLocation)
}
// DO NOT call super.mouseDown - this prevents the event from propagating
}
override func mouseDragged(with event: NSEvent) {
guard let app = screenshotApp else {
// Even if app is nil, consume the event to prevent it from reaching other windows
return
}
if app.isMultiMonitorSelectionActive && app.isDragging {
let globalLocation = NSEvent.mouseLocation
app.hasMouseMoved = true // MARK THAT MOUSE HAS MOVED
app.updateDragSelection(to: globalLocation)
}
// DO NOT call super.mouseDragged - this prevents the event from propagating
}
override func mouseUp(with event: NSEvent) {
guard let app = screenshotApp else {
// Even if app is nil, consume the event to prevent it from reaching other windows
return
}
if app.isMultiMonitorSelectionActive && app.isDragging {
let globalLocation = NSEvent.mouseLocation
// CHECK FOR SINGLE CLICK FIRST
let timeSinceMouseDown = CACurrentMediaTime() - app.mouseDownTime
let distanceMoved = sqrt(pow(globalLocation.x - app.mouseDownLocation.x, 2) + pow(globalLocation.y - app.mouseDownLocation.y, 2))
print("🎯 EventCaptureView - mouse up at: \(globalLocation)")
print("🎯 Time since down: \(timeSinceMouseDown)s, distance: \(distanceMoved)px, moved: \(app.hasMouseMoved), allScreenModifierPressed: \(app.isAllScreenModifierPressed), allScreensToggled: \(app.isAllScreensCaptureToggledOn)")
// 1. NIEUW: Als "alle schermen" modus is GETOGGLED AAN, en het is een klik (geen drag)
if app.isAllScreensCaptureToggledOn && !app.hasMouseMoved && distanceMoved < 5.0 && timeSinceMouseDown < 0.5 {
print("🎯 Click detected (toggle ON) - capturing all screens")
app.deactivateMultiMonitorSelection()
app.captureAllScreens() // captureAllScreens reset zelf de toggle
app.resetTrackingVariables()
return
}
// 2. Prioriteit: Single click voor huidige scherm (als toggle NIET aanstaat)
if !app.hasMouseMoved && distanceMoved < 5.0 && timeSinceMouseDown < 0.5 && !app.isAllScreenModifierPressed {
print("🎯 Single click detected (toggle OFF) - capturing current screen")
app.deactivateMultiMonitorSelection()
app.captureCurrentScreen(at: globalLocation)
app.resetTrackingVariables() // RESET for clean state
return
}
// 4. Anders, normale selectie beëindigen
print("🎯 Ending drag selection normally")
app.endDragSelection(at: globalLocation)
// Show crosshairs again after drag ends
showCrosshairs()
}
// DO NOT call super.mouseUp - this prevents the event from propagating
}
override func rightMouseDown(with event: NSEvent) {
// Handle right-click to cancel selection
guard let app = screenshotApp else { return }
if app.isMultiMonitorSelectionActive {
print("🎯 Event capture - right-click, canceling selection")
app.cancelMultiMonitorSelection()
}
// DO NOT call super.rightMouseDown - this prevents the event from propagating
}
override func rightMouseUp(with event: NSEvent) {
// Consume right mouse up events during selection
// DO NOT call super.rightMouseUp - this prevents the event from propagating
}
override func otherMouseDown(with event: NSEvent) {
// Consume other mouse button events during selection
// DO NOT call super.otherMouseDown - this prevents the event from propagating
}
override func otherMouseUp(with event: NSEvent) {
// Consume other mouse button events during selection
// DO NOT call super.otherMouseUp - this prevents the event from propagating
}
override func scrollWheel(with event: NSEvent) {
// Consume scroll wheel events during selection
// DO NOT call super.scrollWheel - this prevents the event from propagating
}
override func keyDown(with event: NSEvent) {
// Only handle ESC key if multi-monitor selection is active
if event.keyCode == 53 && screenshotApp?.isMultiMonitorSelectionActive == true { // ESC key
screenshotApp?.cancelMultiMonitorSelection()
return // Consume event
}
// The Command-key handling has been moved to the flagsChanged handler
// in MultiMonitorSystem.swift to implement the toggle correctly.
super.keyDown(with: event)
}
override func keyUp(with event: NSEvent) {
// ... existing code ...
super.keyUp(with: event)
}
override var acceptsFirstResponder: Bool {
return true
}
override func becomeFirstResponder() -> Bool {
print("⌨️ EventCaptureView becoming first responder")
return super.becomeFirstResponder()
}
private func hideCrosshairs() {
// Find all crosshair views in the window hierarchy and hide them
if let window = self.window {
findAndHideCrosshairs(in: window.contentView)
}
}
private func showCrosshairs() {
// Find all crosshair views in the window hierarchy and show them
if let window = self.window {
findAndShowCrosshairs(in: window.contentView)
}
}
private func findAndHideCrosshairs(in view: NSView?) {
guard let view = view else { return }
for subview in view.subviews {
if let crosshair = subview as? CrosshairCursorView {
crosshair.stopTracking()
}
findAndHideCrosshairs(in: subview)
}
}
private func findAndShowCrosshairs(in view: NSView?) {
guard let view = view else { return }
for subview in view.subviews {
if let crosshair = subview as? CrosshairCursorView {
crosshair.isHidden = false
crosshair.startTracking()
}
findAndShowCrosshairs(in: subview)
}
}
}