@@ -36,6 +36,11 @@ final class PickerSession: NSObject, SCContentSharingPickerObserver, @unchecked
3636 private var picked : PickedSource ?
3737 /// Whether the picker, as last shown, left out the whole app that owns this helper.
3838 private var presentedExcludesApp = false
39+ /// Whether a `present` is waiting for its answer. While the shared picker is active,
40+ /// macOS can deliver a selection outside any presentation of ours (the menu-bar
41+ /// sharing item, or a pick replayed from an earlier session): that is not an answer to
42+ /// anything the app asked, and reporting it recorded a window nobody chose (#1013).
43+ private var awaitingPick = false
3944 private var recorder : ScreenCaptureRecorder ?
4045
4146 func run ( ) -> Never {
@@ -107,9 +112,17 @@ final class PickerSession: NSObject, SCContentSharingPickerObserver, @unchecked
107112 configuration . excludedBundleIDs = [ notificationCenterBundleID ] + [ appBundleID ] . compactMap { $0 }
108113 presentedExcludesApp = appBundleID != nil
109114
115+ // Off, then on: the shared picker keeps its state across presentations, and a
116+ // picker left active from an earlier pick can hand that pick back as a new answer
117+ // (#1013). Each presentation starts from a fresh activation -- unless a take is
118+ // running, whose stream deactivating the picker could end.
110119 let picker = SCContentSharingPicker . shared
120+ if recorder == nil {
121+ picker . isActive = false
122+ }
111123 picker . defaultConfiguration = configuration
112124 picker . isActive = true
125+ awaitingPick = true
113126 picker . present ( )
114127 emit ( [ " event " : " picker-presented " ] )
115128 }
@@ -126,14 +139,16 @@ final class PickerSession: NSObject, SCContentSharingPickerObserver, @unchecked
126139 DispatchQueue . main . async {
127140 // A cancel for a running stream is the user stopping it from the menu bar, which
128141 // the take's own stop path already reports.
129- if stream == nil {
142+ if stream == nil , self . awaitingPick {
143+ self . awaitingPick = false
130144 emit ( [ " event " : " picker-cancelled " ] )
131145 }
132146 }
133147 }
134148
135149 func contentSharingPickerStartDidFailWithError ( _ error : Error ) {
136150 DispatchQueue . main . async {
151+ self . awaitingPick = false
137152 emit ( [
138153 " event " : " picker-failed " ,
139154 " message " : " \( error ) " ,
@@ -147,6 +162,16 @@ final class PickerSession: NSObject, SCContentSharingPickerObserver, @unchecked
147162 guard stream == nil else {
148163 return
149164 }
165+ // A pick nobody asked for must not replace the retained one either.
166+ guard awaitingPick else {
167+ emit ( [
168+ " event " : " warning " ,
169+ " code " : " picker-unrequested-pick " ,
170+ " message " : " Ignored a picker selection made outside a presentation. " ,
171+ ] )
172+ return
173+ }
174+ awaitingPick = false
150175
151176 let display = filter . includedDisplays . first
152177 let window = filter . includedWindows . first
0 commit comments