Skip to content

Commit 56316d3

Browse files
committed
fix: reject camera formats from stale device queries
1 parent e370c25 commit 56316d3

2 files changed

Lines changed: 65 additions & 7 deletions

File tree

apps/desktop-gpui/src/devices.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,23 @@ pub struct DeviceSnapshot {
135135
}
136136

137137
impl DeviceSnapshot {
138+
pub fn update_camera_formats(
139+
&mut self,
140+
queried: &CameraOption,
141+
formats: Vec<CameraFormat>,
142+
) -> bool {
143+
let Some(camera) = self
144+
.cameras
145+
.iter_mut()
146+
.find(|camera| camera.same_device(queried))
147+
else {
148+
return false;
149+
};
150+
camera.best_format = formats.first().copied();
151+
camera.formats = formats;
152+
true
153+
}
154+
138155
/// Enumerate everything. This blocks — AVFoundation camera discovery and the
139156
/// window-server queries are both slow enough to drop frames — so callers
140157
/// should run it on the background executor, never inside `render`.
@@ -282,6 +299,48 @@ mod input_enumeration_tests {
282299
}]
283300
}
284301

302+
#[test]
303+
fn completed_query_updates_matching_camera_capabilities() {
304+
let queried = camera_fixture("camera-a");
305+
let mut snapshot = DeviceSnapshot {
306+
cameras: vec![queried.clone()],
307+
..Default::default()
308+
};
309+
assert!(snapshot.update_camera_formats(&queried, formats_fixture()));
310+
assert_eq!(snapshot.cameras[0].formats, formats_fixture());
311+
assert_eq!(
312+
snapshot.cameras[0].best_format,
313+
formats_fixture().first().copied()
314+
);
315+
}
316+
317+
#[test]
318+
fn completed_query_cannot_overwrite_replacement_camera_capabilities() {
319+
let queried = camera_fixture("camera-a");
320+
let mut changed_id = queried.clone();
321+
changed_id.device_id = "camera-b".into();
322+
let mut changed_model = queried.clone();
323+
changed_model.model_id =
324+
Some(cap_camera::ModelID::try_from("046d:08e5".to_string()).unwrap());
325+
let mut changed_label = queried.clone();
326+
changed_label.label = "Replacement camera".into();
327+
for replacement in [changed_id, changed_model, changed_label] {
328+
let mut snapshot = DeviceSnapshot {
329+
cameras: vec![replacement.clone()],
330+
..Default::default()
331+
};
332+
assert!(!snapshot.update_camera_formats(&queried, formats_fixture()));
333+
assert_eq!(snapshot.cameras, vec![replacement]);
334+
}
335+
}
336+
337+
#[test]
338+
fn completed_query_cannot_restore_disconnected_camera() {
339+
let mut snapshot = DeviceSnapshot::default();
340+
assert!(!snapshot.update_camera_formats(&camera_fixture("camera-a"), formats_fixture()));
341+
assert!(snapshot.cameras.is_empty());
342+
}
343+
285344
#[test]
286345
fn repeated_camera_refresh_does_not_reopen_unchanged_devices() {
287346
let mut previous = vec![camera_fixture("camera-a").with_formats(&[], formats_fixture)];

apps/desktop-gpui/src/main_window.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5048,14 +5048,13 @@ impl MainWindow {
50485048
}
50495049
if let Some(formats) = camera_formats
50505050
&& let Some(DeviceFormatTarget::Camera(camera)) = &this.device_format_target
5051-
&& let Some(device) = this
5052-
.devices
5053-
.cameras
5054-
.iter_mut()
5055-
.find(|device| device.device_id == camera.device_id)
5051+
&& !this.devices.update_camera_formats(camera, formats)
50565052
{
5057-
device.best_format = formats.first().copied();
5058-
device.formats = formats;
5053+
this.device_formats = Some(Err(
5054+
"Camera changed or disconnected. Reopen its format settings.".into(),
5055+
));
5056+
cx.notify();
5057+
return;
50595058
}
50605059
this.device_format_notice = notice;
50615060
this.device_formats = Some(formats);

0 commit comments

Comments
 (0)