Skip to content

Commit c02ede8

Browse files
committed
fix: hide partially rendered scan windows before cursor restore
1 parent cd03a4e commit c02ede8

1 file changed

Lines changed: 55 additions & 20 deletions

File tree

src-tauri/src/scanning_runtime.rs

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,8 @@ fn reset_scanner<A: Adapter>(app: &AppHandle, message: &str) {
199199
c.data.lock().unwrap_or_else(|p| p.into_inner()).message =
200200
"Input cleanup will be retried before scanning resumes.".into();
201201
}
202-
let _ = render_tiles(&[]);
203202
app.state::<switch_runtime::Controller>().stop();
204-
HOST.with(|host| {
205-
if let Some(host) = host.borrow_mut().as_mut() {
206-
host.hide();
207-
}
208-
});
209-
hide_prompt();
203+
hide_scan_visuals();
210204
let token = c
211205
.data
212206
.lock()
@@ -423,13 +417,7 @@ fn dispatch<A: Adapter>(
423417
if !c.enabled.load(Ordering::SeqCst) || !input_active(app, input_generation, remote) {
424418
return Err("Scan action was cancelled.".into());
425419
}
426-
render_tiles(&[])?;
427-
hide_prompt();
428-
HOST.with(|slot| {
429-
if let Some(host) = slot.borrow_mut().as_mut() {
430-
host.hide();
431-
}
432-
});
420+
hide_scan_visuals();
433421
if let Err(error) = A::activate(app, request) {
434422
A::cleanup(app)?;
435423
let mut d = c.data.lock().unwrap_or_else(|p| p.into_inner());
@@ -464,6 +452,37 @@ fn render_countdown(countdown: Option<&crate::scanning::Countdown>) -> Result<()
464452
Ok(())
465453
})
466454
}
455+
456+
// Rendering can fail after a native window is shown but before its cache is
457+
// committed. Cleanup must never rely on that cache to decide whether to hide.
458+
fn clear_cached_visual<H, C: Default>(state: &mut (H, C), hide: impl FnOnce(&mut H)) {
459+
hide(&mut state.0);
460+
state.1 = C::default();
461+
}
462+
463+
fn hide_scan_visuals() {
464+
for slot in [&HOST, &PROMPT, &LABEL] {
465+
slot.with(|host| {
466+
if let Some(host) = host.borrow_mut().as_mut() {
467+
host.hide();
468+
}
469+
});
470+
}
471+
TILES.with(|slot| {
472+
clear_cached_visual(&mut slot.borrow_mut(), |hosts| {
473+
for host in hosts {
474+
host.hide();
475+
}
476+
})
477+
});
478+
COUNTDOWN.with(|slot| {
479+
clear_cached_visual(&mut slot.borrow_mut(), |host| {
480+
if let Some(host) = host {
481+
host.hide();
482+
}
483+
})
484+
});
485+
}
467486
fn render_tiles(tiles: &[crate::scanning::FrameTile]) -> Result<(), String> {
468487
TILES.with(|slot| {
469488
let mut slot = slot.borrow_mut();
@@ -492,6 +511,13 @@ fn render<A: Adapter>(
492511
let mut d = c.data.lock().unwrap_or_else(|p| p.into_inner());
493512
let cursor = app.state::<crate::overlay::CursorOverlay>();
494513
let active = d.engine.as_ref().is_some_and(Session::active) || prompt.is_some();
514+
if !active {
515+
hide_scan_visuals();
516+
if let Some((token, _)) = d.cursor_suppression.take() {
517+
cursor.release_scan(token);
518+
}
519+
return Ok(());
520+
}
495521
if active {
496522
let (token, started) = match d.cursor_suppression {
497523
Some(lease) => lease,
@@ -524,12 +550,6 @@ fn render<A: Adapter>(
524550
render_countdown(frame.countdown.as_ref())?;
525551
render_label(frame.label_for_prompt(prompt.is_some()), &frame.tiles)?;
526552
show_prompt(app, prompt)?;
527-
if !active {
528-
// Native scanning windows have all been hidden before the cursor can return.
529-
if let Some((token, _)) = d.cursor_suppression.take() {
530-
cursor.release_scan(token);
531-
}
532-
}
533553
Ok(())
534554
}
535555
fn tick<A: Adapter>(app: &AppHandle) {
@@ -889,6 +909,21 @@ pub fn restart_point_on_display(app: &AppHandle, next: bool) -> Result<(), Strin
889909

890910
#[cfg(test)]
891911
mod ownership_tests {
912+
#[test]
913+
fn failed_partial_presentations_are_hidden_even_with_empty_caches() {
914+
let mut tiles = (vec![true, true, false], Vec::<u8>::new());
915+
super::clear_cached_visual(&mut tiles, |hosts| hosts.fill(false));
916+
assert!(tiles.0.iter().all(|visible| !visible));
917+
assert!(tiles.1.is_empty());
918+
let mut countdown = (Some(true), None::<u8>);
919+
super::clear_cached_visual(&mut countdown, |host| {
920+
if let Some(visible) = host {
921+
*visible = false;
922+
}
923+
});
924+
assert_eq!(countdown, (Some(false), None));
925+
}
926+
892927
#[test]
893928
fn stopped_remote_input_cannot_fall_back_to_a_matching_local_generation() {
894929
assert!(!super::source_is_current(true, false, true));

0 commit comments

Comments
 (0)