Skip to content

Commit fed3a45

Browse files
committed
fix(mac): reset Apple's picker before each presentation and ignore unrequested picks
1 parent d433e7c commit fed3a45

1 file changed

Lines changed: 26 additions & 1 deletion

File tree

‎electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/PickerSession.swift‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)