Skip to content

Commit 2b8ef70

Browse files
committed
feat: Update emergency stop functionality and adjust visual feedback settings
1 parent d5495c7 commit 2b8ef70

File tree

9 files changed

+267
-129
lines changed

9 files changed

+267
-129
lines changed

Sources/ClickIt/Core/Click/ClickCoordinator.swift

Lines changed: 3 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -60,20 +60,7 @@ class ClickCoordinator: ObservableObject {
6060
// Start real-time elapsed time tracking
6161
timeManager.startTracking()
6262

63-
// Show visual feedback overlay if enabled
64-
if configuration.showVisualFeedback {
65-
if configuration.useDynamicMouseTracking {
66-
print("ClickCoordinator: Starting dynamic automation with visual feedback")
67-
// For dynamic mode, show overlay at current mouse position (in AppKit coordinates)
68-
let currentAppKitPosition = NSEvent.mouseLocation
69-
VisualFeedbackOverlay.shared.showOverlay(at: currentAppKitPosition, isActive: true)
70-
} else {
71-
print("ClickCoordinator: Starting fixed automation with visual feedback at \(configuration.location)")
72-
VisualFeedbackOverlay.shared.showOverlay(at: configuration.location, isActive: true)
73-
}
74-
} else {
75-
print("ClickCoordinator: Starting automation without visual feedback")
76-
}
63+
print("ClickCoordinator: Starting automation at \(configuration.location)")
7764

7865
automationTask = Task {
7966
await runAutomationLoop(configuration: configuration)
@@ -98,11 +85,6 @@ class ClickCoordinator: ObservableObject {
9885
// Stop real-time elapsed time tracking
9986
timeManager.stopTracking()
10087

101-
// Hide visual feedback overlay immediately
102-
print("ClickCoordinator: About to hide visual feedback overlay")
103-
VisualFeedbackOverlay.shared.hideOverlay()
104-
print("ClickCoordinator: Visual feedback overlay hidden")
105-
10688
automationConfig = nil
10789
print("ClickCoordinator: stopAutomation() completed")
10890
}
@@ -121,7 +103,6 @@ class ClickCoordinator: ObservableObject {
121103

122104
// Priority cleanup - all operations must be synchronous for speed
123105
timeManager.stopTracking()
124-
VisualFeedbackOverlay.shared.hideOverlay()
125106
automationConfig = nil
126107

127108
print("ClickCoordinator: EMERGENCY STOP completed")
@@ -334,20 +315,6 @@ class ClickCoordinator: ObservableObject {
334315

335316
print("ClickCoordinator: Executing automation step at \(location) (dynamic: \(configuration.useDynamicMouseTracking))")
336317

337-
// Update visual feedback overlay if enabled
338-
if configuration.showVisualFeedback {
339-
await MainActor.run {
340-
if configuration.useDynamicMouseTracking {
341-
// Convert back to AppKit coordinates for overlay positioning
342-
let appKitLocation = convertCoreGraphicsToAppKitMultiMonitor(location)
343-
print("[Dynamic Debug] Overlay position (AppKit): \(appKitLocation)")
344-
VisualFeedbackOverlay.shared.updateOverlay(at: appKitLocation, isActive: true)
345-
} else {
346-
VisualFeedbackOverlay.shared.updateOverlay(at: location, isActive: true)
347-
}
348-
}
349-
}
350-
351318
// Perform the actual click
352319
print("ClickCoordinator: Performing actual click at \(location)")
353320
let result: ClickResult
@@ -369,19 +336,6 @@ class ClickCoordinator: ObservableObject {
369336

370337
print("ClickCoordinator: Click result: success=\(result.success)")
371338

372-
// Show click pulse for successful clicks
373-
if configuration.showVisualFeedback && result.success {
374-
await MainActor.run {
375-
if configuration.useDynamicMouseTracking {
376-
// Convert back to AppKit coordinates for pulse positioning
377-
let appKitLocation = convertCoreGraphicsToAppKitMultiMonitor(location)
378-
VisualFeedbackOverlay.shared.showClickPulse(at: appKitLocation)
379-
} else {
380-
VisualFeedbackOverlay.shared.showClickPulse(at: location)
381-
}
382-
}
383-
}
384-
385339
return result
386340
}
387341

@@ -486,7 +440,6 @@ struct AutomationConfiguration {
486440
let stopOnError: Bool
487441
let randomizeLocation: Bool
488442
let locationVariance: CGFloat
489-
let showVisualFeedback: Bool
490443
let useDynamicMouseTracking: Bool
491444

492445
init(
@@ -499,7 +452,6 @@ struct AutomationConfiguration {
499452
stopOnError: Bool = false,
500453
randomizeLocation: Bool = false,
501454
locationVariance: CGFloat = 0,
502-
showVisualFeedback: Bool = true,
503455
useDynamicMouseTracking: Bool = false
504456
) {
505457
self.location = location
@@ -511,7 +463,6 @@ struct AutomationConfiguration {
511463
self.stopOnError = stopOnError
512464
self.randomizeLocation = randomizeLocation
513465
self.locationVariance = locationVariance
514-
self.showVisualFeedback = showVisualFeedback
515466
self.useDynamicMouseTracking = useDynamicMouseTracking
516467
}
517468
}
@@ -536,13 +487,11 @@ extension ClickCoordinator {
536487
/// - location: Location to click
537488
/// - interval: Interval between clicks
538489
/// - maxClicks: Maximum number of clicks (optional)
539-
/// - showVisualFeedback: Whether to show visual feedback overlay
540-
func startSimpleAutomation(at location: CGPoint, interval: TimeInterval, maxClicks: Int? = nil, showVisualFeedback: Bool = true) {
490+
func startSimpleAutomation(at location: CGPoint, interval: TimeInterval, maxClicks: Int? = nil) {
541491
let config = AutomationConfiguration(
542492
location: location,
543493
clickInterval: interval,
544494
maxClicks: maxClicks,
545-
showVisualFeedback: showVisualFeedback,
546495
useDynamicMouseTracking: false
547496
)
548497
startAutomation(with: config)
@@ -554,21 +503,18 @@ extension ClickCoordinator {
554503
/// - interval: Interval between clicks
555504
/// - variance: Location randomization variance
556505
/// - maxClicks: Maximum number of clicks (optional)
557-
/// - showVisualFeedback: Whether to show visual feedback overlay
558506
func startRandomizedAutomation(
559507
at location: CGPoint,
560508
interval: TimeInterval,
561509
variance: CGFloat,
562-
maxClicks: Int? = nil,
563-
showVisualFeedback: Bool = true
510+
maxClicks: Int? = nil
564511
) {
565512
let config = AutomationConfiguration(
566513
location: location,
567514
clickInterval: interval,
568515
maxClicks: maxClicks,
569516
randomizeLocation: true,
570517
locationVariance: variance,
571-
showVisualFeedback: showVisualFeedback,
572518
useDynamicMouseTracking: false
573519
)
574520
startAutomation(with: config)

Sources/ClickIt/Core/Hotkeys/HotkeyManager.swift

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -130,39 +130,36 @@ class HotkeyManager: ObservableObject {
130130
// PRIORITY PATH: Direct synchronous emergency stop for <50ms guarantee
131131
print("HotkeyManager: EMERGENCY STOP activated (\(config.description))")
132132

133-
// Immediate synchronous stop - no async overhead
134-
DispatchQueue.main.sync {
135-
let coordinator = ClickCoordinator.shared
133+
// CRITICAL FIX: We're already on @MainActor, so call coordinator methods directly
134+
// without additional dispatch to avoid deadlock
135+
let coordinator = ClickCoordinator.shared
136+
137+
if coordinator.isActive {
138+
// Call emergency stop method directly - no dispatch needed since we're on MainActor
139+
coordinator.emergencyStopAutomation()
136140

137-
if coordinator.isActive {
138-
// Call priority emergency stop method for immediate termination
139-
coordinator.emergencyStopAutomation()
141+
// Track response time
142+
if let tracker = self.responseTimeTracker {
143+
let responseTime = tracker.stopTracking()
144+
print("HotkeyManager: Emergency stop response time: \(responseTime)ms")
140145

141-
// Show visual confirmation immediately after stopping
142-
VisualFeedbackOverlay.shared.showEmergencyStopConfirmation()
143-
144-
// Track response time
145-
if let tracker = self.responseTimeTracker {
146-
let responseTime = tracker.stopTracking()
147-
print("HotkeyManager: Emergency stop response time: \(responseTime)ms")
148-
149-
// Log warning if response time exceeds target
150-
if responseTime > 50.0 {
151-
print("⚠️ HotkeyManager: Emergency stop response time exceeded 50ms target: \(responseTime)ms")
152-
}
153-
}
154-
} else {
155-
print("HotkeyManager: Emergency stop triggered but no automation running")
156-
157-
// Still track response time for diagnostic purposes
158-
if let tracker = self.responseTimeTracker {
159-
let responseTime = tracker.stopTracking()
160-
print("HotkeyManager: Emergency stop response time (no automation): \(responseTime)ms")
146+
// Log warning if response time exceeds target
147+
if responseTime > 50.0 {
148+
print("⚠️ HotkeyManager: Emergency stop response time exceeded 50ms target: \(responseTime)ms")
161149
}
162150
}
151+
} else {
152+
print("HotkeyManager: Emergency stop triggered but no automation running")
153+
154+
// Still track response time for diagnostic purposes
155+
if let tracker = self.responseTimeTracker {
156+
let responseTime = tracker.stopTracking()
157+
print("HotkeyManager: Emergency stop response time (no automation): \(responseTime)ms")
158+
}
163159
}
164160

165-
// Reset emergency stop state after brief visual feedback period (async is fine here)
161+
// Reset emergency stop state after brief visual feedback period
162+
// Use async dispatch since this is not time-critical
166163
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
167164
self.emergencyStopActivated = false
168165
self.responseTimeTracker = nil
@@ -229,8 +226,7 @@ class HotkeyManager: ObservableObject {
229226
let testConfig = AutomationConfiguration(
230227
location: CGPoint(x: 100, y: 100),
231228
clickInterval: 5.0, // Long interval for testing
232-
maxClicks: 1000,
233-
showVisualFeedback: false
229+
maxClicks: 1000
234230
)
235231

236232
ClickCoordinator.shared.startAutomation(with: testConfig)
@@ -399,9 +395,9 @@ struct HotkeyConfiguration {
399395
let description: String
400396

401397
static let `default` = HotkeyConfiguration(
402-
keyCode: FrameworkConstants.CarbonConfig.deleteKeyCode,
403-
modifiers: 0, // No modifiers for DELETE key
404-
description: "DELETE Key"
398+
keyCode: 122, // F1 key
399+
modifiers: UInt32(NSEvent.ModifierFlags.shift.rawValue), // Shift modifier
400+
description: "Shift + F1"
405401
)
406402
}
407403

@@ -428,6 +424,12 @@ extension HotkeyConfiguration {
428424
description: "F1 Key"
429425
)
430426

427+
static let shiftF1Key = HotkeyConfiguration(
428+
keyCode: 122, // F1 key
429+
modifiers: UInt32(NSEvent.ModifierFlags.shift.rawValue),
430+
description: "Shift + F1"
431+
)
432+
431433
static let spaceKey = HotkeyConfiguration(
432434
keyCode: 49, // Space key
433435
modifiers: 0,
@@ -457,13 +459,7 @@ extension HotkeyConfiguration {
457459
// MARK: - All Available Emergency Stop Keys
458460

459461
static let allEmergencyStopKeys: [HotkeyConfiguration] = [
460-
.escapeKey, // ESC - Universal emergency stop
461-
.deleteKey, // DELETE - Current default
462-
.f1Key, // F1 - Easy to reach function key
463-
.spaceKey, // Space - Large, easy target
464-
.cmdPeriod, // Cmd+Period - macOS standard cancel
465-
.cmdDelete, // Cmd+DELETE - Modified delete
466-
.optionDelete // Option+DELETE - Alternative modifier
462+
.shiftF1Key // Shift + F1 - Single emergency stop key
467463
]
468464

469465
// MARK: - Key Code Constants

Sources/ClickIt/Core/Models/ClickSettings.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,7 @@ class ClickSettings: ObservableObject {
226226
maxDuration: maxDurationValue,
227227
stopOnError: stopOnError,
228228
randomizeLocation: randomizeLocation,
229-
locationVariance: CGFloat(locationVariance),
230-
showVisualFeedback: showVisualFeedback
229+
locationVariance: CGFloat(locationVariance)
231230
)
232231
}
233232
}

Sources/ClickIt/UI/Components/FooterInfoCard.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct FooterInfoCard: View {
1111
.font(.system(size: 12))
1212
.foregroundColor(.secondary)
1313

14-
Text("DELETE to stop")
14+
Text("Shift+F1 to stop")
1515
.font(.caption)
1616
.foregroundColor(.secondary)
1717

0 commit comments

Comments
 (0)