@@ -37,6 +37,9 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
3737 @Flag ( help: " Right-click (secondary click) " )
3838 var right = false
3939
40+ @Flag ( help: " Focus target and send a foreground mouse click " )
41+ var foreground = false
42+
4043 @OptionGroup var focusOptions : FocusCommandOptions
4144
4245 @RuntimeStorage private var runtime : CommandRuntime ?
@@ -65,6 +68,20 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
6568 self . runtime? . configuration. jsonOutput ?? self . runtimeOptions. jsonOutput
6669 }
6770
71+ private var deliveryMode : ClickDeliveryMode {
72+ if self . focusOptions. backgroundDeliveryExplicitlyRequested {
73+ return . background
74+ }
75+ if self . foreground || self . focusOptions. hasForegroundFocusOverrides {
76+ return . foreground
77+ }
78+ return . background
79+ }
80+
81+ private var usesBackgroundDelivery : Bool {
82+ self . deliveryMode == . background
83+ }
84+
6885 @MainActor
6986 mutating func run( using runtime: CommandRuntime ) async throws {
7087 self . runtime = runtime
@@ -106,7 +123,7 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
106123 // InputDriver.click() sends a CGEvent at screen-absolute coordinates,
107124 // so if the target window is not frontmost, the click will land on
108125 // whatever window is at that position (see #90).
109- if !self . focusOptions . focusBackground {
126+ if !self . usesBackgroundDelivery {
110127 try await self . verifyFocusForCoordinateClick ( coordinateResolution: resolvedCoordinates)
111128 }
112129
@@ -126,7 +143,7 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
126143 let elementId = self . on ?? self . id
127144
128145 if let elementId {
129- if !self . focusOptions . focusBackground {
146+ if !self . usesBackgroundDelivery {
130147 observation = try await InteractionObservationRefresher . refreshForMissingElementsIfNeeded (
131148 observation,
132149 elementIds: [ elementId] ,
@@ -139,7 +156,7 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
139156 activeSnapshotId = observation. snapshotId ?? " "
140157
141158 clickTarget = . elementId( elementId)
142- if self . focusOptions . focusBackground {
159+ if self . usesBackgroundDelivery {
143160 let element = try await self . cachedElementById ( elementId, observation: observation)
144161 waitResult = WaitForElementResult ( found: true , element: element, waitTime: 0 )
145162 } else {
@@ -157,13 +174,13 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
157174 }
158175
159176 } else if let searchQuery = query {
160- if !self . focusOptions . focusBackground {
177+ if !self . usesBackgroundDelivery {
161178 observation = try await self . refreshObservationIfQueryMissing ( observation, query: searchQuery)
162179 }
163180 observationForInvalidation = observation
164181 activeSnapshotId = observation. snapshotId ?? " "
165182
166- if self . focusOptions . focusBackground {
183+ if self . usesBackgroundDelivery {
167184 let element = try await self . cachedElementMatching ( searchQuery, observation: observation)
168185 clickTarget = . elementId( element. id)
169186 waitResult = WaitForElementResult ( found: true , element: element, waitTime: 0 )
@@ -194,7 +211,12 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
194211
195212 // Determine click type
196213 let clickType : ClickType = self . right ? . right : ( self . double ? . double : . single)
197- try await self . performClick ( clickTarget, clickType: clickType, snapshotId: activeSnapshotId)
214+ try await self . performClick (
215+ clickTarget,
216+ clickType: clickType,
217+ snapshotId: activeSnapshotId,
218+ coordinateResolution: coordinateResolution
219+ )
198220
199221 // Brief delay to ensure click is processed
200222 try await Task . sleep ( nanoseconds: 20_000_000 ) // 0.02 seconds
@@ -224,7 +246,8 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
224246 coordinateSpace: coordinateResolution? . coordinateSpace. rawValue,
225247 inputCoordinates: coordinateResolution? . inputPoint,
226248 screenCoordinates: coordinateResolution? . screenPoint,
227- targetPoint: details. targetPointDiagnostics
249+ targetPoint: details. targetPointDiagnostics,
250+ deliveryMode: self . deliveryMode. rawValue
228251 )
229252
230253 if let observationForInvalidation {
@@ -312,7 +335,7 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
312335 return " window \( windowID) "
313336 }
314337
315- guard self . focusOptions . focusBackground else {
338+ guard self . usesBackgroundDelivery else {
316339 return await self . frontmostApplicationName ( )
317340 }
318341
@@ -329,6 +352,14 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
329352 guard !snapshotId. isEmpty,
330353 let snapshot = try ? await self . services. snapshots. getUIAutomationSnapshot ( snapshotId: snapshotId)
331354 else {
355+ if let detectionResult = try ? await self . services. snapshots. getDetectionResult ( snapshotId: snapshotId) {
356+ if let applicationName = detectionResult. metadata. windowContext? . applicationName {
357+ return applicationName
358+ }
359+ if let processId = detectionResult. metadata. windowContext? . applicationProcessId {
360+ return await self . applicationName ( processIdentifier: processId) ?? " PID \( processId) "
361+ }
362+ }
332363 return await self. frontmostApplicationName ( )
333364 }
334365
@@ -354,8 +385,8 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
354385 output ( result) {
355386 print ( " ✅ Click successful " )
356387 print ( " 🎯 App: \( result. targetApp) " )
357- if self . focusOptions . focusBackground {
358- print ( " 🎯 Mode: background " )
388+ if let deliveryMode = result . deliveryMode {
389+ print ( " 🎯 Mode: \( deliveryMode ) " )
359390 }
360391 if let coordinateSpace = result. coordinateSpace {
361392 print ( " 🎯 Coordinate space: \( coordinateSpace) " )
@@ -456,15 +487,23 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
456487 return score
457488 }
458489
459- private func performClick( _ target: ClickTarget , clickType: ClickType , snapshotId: String ) async throws {
490+ private func performClick(
491+ _ target: ClickTarget,
492+ clickType: ClickType,
493+ snapshotId: String,
494+ coordinateResolution: InteractionCoordinateResolution?
495+ ) async throws {
460496 let effectiveSnapshotId : String ? = if case . coordinates = target {
461497 nil
462498 } else {
463499 snapshotId. isEmpty ? nil : snapshotId
464500 }
465501
466- if self . focusOptions. focusBackground {
467- let pid = try await self . resolveBackgroundClickProcessIdentifier ( snapshotId: effectiveSnapshotId)
502+ if self . usesBackgroundDelivery {
503+ let pid = try await self . resolveBackgroundClickProcessIdentifier (
504+ snapshotId: effectiveSnapshotId,
505+ coordinateResolution: coordinateResolution
506+ )
468507 try await AutomationServiceBridge . click (
469508 automation: self . services. automation,
470509 target: target,
@@ -486,7 +525,7 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
486525 snapshotId: String? ,
487526 coordinateResolution: InteractionCoordinateResolution? = nil
488527 ) async throws {
489- if self . focusOptions . focusBackground {
528+ if self . usesBackgroundDelivery {
490529 try self . validateBackgroundClickOptions ( )
491530 return
492531 }
@@ -523,17 +562,22 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
523562 }
524563
525564 private func validateBackgroundClickOptions( ) throws {
526- if self . focusOptions. focusTimeoutSeconds != nil ||
527- self . focusOptions. focusRetryCount != nil ||
528- self . focusOptions. spaceSwitch ||
529- self . focusOptions. bringToCurrentSpace {
565+ if self . foreground, self . focusOptions. backgroundDeliveryExplicitlyRequested {
566+ throw ValidationError ( " --foreground cannot be combined with --focus-background " )
567+ }
568+
569+ if self . focusOptions. backgroundDeliveryExplicitlyRequested &&
570+ self . focusOptions. hasForegroundFocusOverrides {
530571 throw ValidationError ( " --focus-background cannot be combined with focus options " )
531572 }
532573 }
533574
534- private func resolveBackgroundClickProcessIdentifier( snapshotId: String ? ) async throws -> pid_t {
575+ private func resolveBackgroundClickProcessIdentifier(
576+ snapshotId: String? ,
577+ coordinateResolution: InteractionCoordinateResolution?
578+ ) async throws -> pid_t {
535579 if self . target. pid != nil , self . target. app != nil {
536- throw ValidationError ( " --focus-background accepts one process target: use --app or --pid" )
580+ throw ValidationError ( " Background click accepts one process target: use --app or --pid" )
537581 }
538582
539583 if let pid = self . target. pid {
@@ -549,14 +593,32 @@ struct ClickCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConf
549593 return pid_t ( app. processIdentifier)
550594 }
551595
596+ if let processId = coordinateResolution? . targetProcessIdentifier {
597+ return pid_t ( processId)
598+ }
599+
552600 if let snapshotId,
553601 let snapshot = try ? await self . services. snapshots. getUIAutomationSnapshot ( snapshotId: snapshotId) ,
554602 let processId = snapshot. applicationProcessId {
555603 return pid_t ( processId)
556604 }
557605
558- throw ValidationError ( " --focus-background requires --app, --pid, or a snapshot with process metadata " )
606+ if let snapshotId,
607+ let detectionResult = try ? await self . services. snapshots. getDetectionResult ( snapshotId: snapshotId) ,
608+ let processId = detectionResult. metadata. windowContext? . applicationProcessId {
609+ return pid_t ( processId)
610+ }
611+
612+ throw ValidationError (
613+ " Background click requires --app, --pid, --window-id, or a snapshot with process metadata; " +
614+ " use --foreground for foreground screen clicks "
615+ )
559616 }
560617
561618 // Error handling is provided by ErrorHandlingCommand protocol
562619}
620+
621+ private enum ClickDeliveryMode: String {
622+ case background
623+ case foreground
624+ }
0 commit comments