Skip to content

Make ButtonInput<Key> more robust to ghost inputs & invalid states. - #25490

Open
CyberspaceDreamn wants to merge 3 commits into
bevyengine:mainfrom
CyberspaceDreamn:patch-keyboard-key-held-after-release
Open

Make ButtonInput<Key> more robust to ghost inputs & invalid states.#25490
CyberspaceDreamn wants to merge 3 commits into
bevyengine:mainfrom
CyberspaceDreamn:patch-keyboard-key-held-after-release

Conversation

@CyberspaceDreamn

Copy link
Copy Markdown
Contributor

Objective

Generally make ButtonInput<Key> more tolerant of edge-cases, like a Key being held down by multiple KeyCodes, Keys being held down by KeyCodes that are no longer held, keyboard layout swaps, & linux locale hotkey weirdness.

Fixes #25483, fixes #25484.
Probably also #25041, but I can't test on macos.

Solution

  • ButtonInput<Key> is now cleared when a window loses keyboard focus, even if all KeyCodes were already released
  • ButtonInput<Key> now keeps track of which KeyCodes are pressed down with each Key
    • Keys only remember a KeyCode while that KeyCode is pressed
    • When all KeyCodes pressing a Key are released, that Key is released

Testing

Manually, with a cargo run --example multiple_text_inputs patched to log ButtonInput<Key>'s state, by doing things like

  • layout swaps mid-keypress
  • holding 2 shift keys at once & only releasing one
  • pressing a KeyCode that maps to a different Key on press & release
  • selecting a non-bevy window to clear an invalid ButtonInput<Key> state

None of these work well/at all on main & they all do with this patch.

New unit tests that replicate most of the above.

@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-Input Player input via keyboard, mouse, gamepad, and more A-Windowing Platform-agnostic interface layer to run your app in D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Aug 20, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in Input Aug 20, 2026
// Release all cached input to avoid having stuck input when switching between windows in os
if !keyboard_focus_lost_reader.is_empty() {
keycode_input.release_all();
key_input.release_all();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree with this choice. It introduces different weirdness but I think that this is the way.

mut key_input: ResMut<ButtonInput<Key>>,
mut keyboard_input_reader: MessageReader<KeyboardInput>,
mut keyboard_focus_lost_reader: MessageReader<KeyboardFocusLost>,
mut held_key_codes: Local<HashMap<KeyCode, Vec<Key>>>,

@alice-i-cecile alice-i-cecile Aug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment on these Locals laying out the core strategy would be really helpful for following the central logic.

Comment thread crates/bevy_input/src/keyboard.rs Outdated
let just_pressed = key_input.just_pressed(logical_key.clone());
key_input.press(logical_key.clone());

if !just_pressed {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure we need to clear just_released in the same fashion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intentionally set just_released because an event releasing the button was just sent & the docs are a bit ambiguous on the intent for when just_released is true.

pub fn just_released(&self, input: T) -> bool

Returns true if the input has been released during the current frame.

Note: This function does not imply information regarding the current state of ButtonInput::pressed or ButtonInput::just_pressed.

It does make more sense though for a ButtonInput to only be released when it is no longer held.

@@ -172,6 +174,8 @@ pub fn keyboard_input_system(
mut key_input: ResMut<ButtonInput<Key>>,
mut keyboard_input_reader: MessageReader<KeyboardInput>,
mut keyboard_focus_lost_reader: MessageReader<KeyboardFocusLost>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local maps aren't cleared during KeyboardFocusLost :)

@alice-i-cecile alice-i-cecile left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good changes, and an important bug fix. There's a couple of holes to patch still though :)

@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Aug 20, 2026
- Ensure `ButtonInput<Key>` is only released when no longer being held.
- Improve documentation of the recently added `KeyCode` -> `Key` reference counting.
- Clear `keyboard_input_system`'s local cache of held keys on `KeyboardFocusLost`.
  - This avoids a very rare bug that could cause some buttons to become sticky.
- Add more unit tests to `bevy_input::keyboard`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Input Player input via keyboard, mouse, gamepad, and more A-Windowing Platform-agnostic interface layer to run your app in C-Bug An unexpected or incorrect behavior D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.

Swapping keyboard layouts can leave Keys pressed after the user has released them. Unfocusing a window does not clear ButtonInput<Key>

2 participants