🎉 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.
This commit is contained in:
280
ShotScreen/Sources/EventCapture.swift
Normal file
280
ShotScreen/Sources/EventCapture.swift
Normal file
@@ -0,0 +1,280 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user