Fix Alt modifier stuck after Alt-Tab on Windows (#52220)
HuaGu-Dragon
created
## Context
Fixes a bug on Windows where the Alt modifier key becomes stuck in the
pressed state after using Alt-Tab to switch away from and back to the
Zed application.
### Root Cause
In `crates/gpui_windows/src/events.rs`, when the window loses focus
during Alt-Tab, the WM_KEYUP event for the Alt key is never delivered to
the application. The pending modifier state in GPUI remains as
`alt=true`. When the window regains focus, there is no synchronization
point to reset this stale state.
## How to Review
In `handle_activate_msg()` (WM_ACTIVATE handler), when the window is
activated:
1. Reset the cached modifier tracking state (`last_reported_modifiers`
and `last_reported_capslock` set to `None`)
2. Query the actual current modifier state from Windows using
`GetKeyState()` APIs
3. Dispatch a `ModifiersChanged` event with the true state to
synchronize GPUI
## Self-Review Checklist
<!-- Check before requesting review: -->
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
I think it's difficult to add tests for this because it's
platform-dependent, but I recorded a video for comparison — sorry I only
have an anime‑style app to show the key presses.
<details>
<summary>Video</summary>
https://github.com/user-attachments/assets/b545178c-b360-4e2b-8d60-d6f95a0b4b79
https://github.com/user-attachments/assets/bc4d41eb-6f42-4040-a588-0fc46c576db7
</details>
Closes #45485
Release Notes:
- Fixed Alt modifier key stuck after Alt-Tab on Windows. Modifier state
is now synchronized when the window regains focus, ensuring correct key
interpretation after window switching.
@@ -725,6 +725,25 @@ impl WindowsWindowInner {
fn handle_activate_msg(self: &Rc<Self>, wparam: WPARAM) -> Option<isize> {
let activated = wparam.loword() > 0;
let this = self.clone();
+
+ // When the window is activated (gains focus), reset the modifier tracking state.
+ // This fixes the issue where Alt-Tab away and back leaves stale modifier state
+ // (especially the Alt key) because Windows doesn't always send key-up events to
+ // windows that have lost focus.
+ if activated {
+ this.state.last_reported_modifiers.set(None);
+ this.state.last_reported_capslock.set(None);
+
+ if let Some(mut func) = this.state.callbacks.input.take() {
+ let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
+ modifiers: current_modifiers(),
+ capslock: current_capslock(),
+ });
+ func(input);
+ this.state.callbacks.input.set(Some(func));
+ }
+ }
+
self.executor
.spawn(async move {
if let Some(mut func) = this.state.callbacks.active_status_change.take() {