Skip to content

fix: fix stuck keys on lost focus#1156

Closed
alandtse wants to merge 1 commit into
community-shaders:devfrom
alandtse:stuck_tab
Closed

fix: fix stuck keys on lost focus#1156
alandtse wants to merge 1 commit into
community-shaders:devfrom
alandtse:stuck_tab

Conversation

@alandtse
Copy link
Copy Markdown
Collaborator

@alandtse alandtse commented Jun 17, 2025

closes #902
closes #862

Summary by CodeRabbit

  • New Features
    • Improved handling of keyboard input after the app window loses focus, including a short debounce period to prevent accidental key actions.
    • Enhanced input reliability by automatically releasing stuck keys and resetting key states when focus is lost.
  • Bug Fixes
    • Resolved issues with stuck keys or unintended input actions when switching between application windows.

Copilot AI review requested due to automatic review settings June 17, 2025 07:37
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 17, 2025

Walkthrough

The changes introduce a debounce mechanism and explicit key state cleanup to improve input handling during and after window focus loss. Key event processing is now gated by a debounce period, and all tracked pressed keys are released upon focus loss, preventing stuck keys and errant input behavior. ImGui input state synchronization is maintained throughout.

Changes

Files/Paths Change Summary
src/Hooks.cpp Removed explicit ImGui input clearing on WM_KILLFOCUS; now only calls Menu::OnFocusLost().
src/Menu.cpp, src/Menu.h Added debounce logic, pressedKeys tracking, ShouldProcessKeyEvents(), and explicit key release on focus loss. Updated input processing to skip key events during debounce.

Sequence Diagram(s)

sequenceDiagram
    participant WindowProc
    participant Menu
    participant ImGui

    WindowProc->>Menu: OnFocusLost()
    Menu->>ImGui: Release all pressed keys (key-up events)
    Menu->>ImGui: Release Tab, Escape, Space, Enter
    Menu->>ImGui: Clear active widget state
    Menu->>Menu: Record lastFocusLossTime

    Note over Menu: During debounce window
    WindowProc->>Menu: ProcessInputEventQueue()
    Menu->>ImGui: Forward key events (UI only)
    Menu-->>WindowProc: Skip app logic for key events

    Note over Menu: After debounce window
    WindowProc->>Menu: ProcessInputEventQueue()
    Menu->>ImGui: Forward key events
    Menu->>Menu: Process key events for app logic
Loading

Assessment against linked issues

Objective Addressed Explanation
Prevent Tab key from being stuck after Tab+Alt sequence (#902)
Release all pressed keys and reset ImGui state on focus loss (#862, #902)
Add debounce to ignore spurious input after focus loss (#862)
Maintain ImGui input state synchronization during focus changes (#862)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Suggested reviewers

  • jiayev

Poem

A hop, a skip, a window lost,
No more stuck keys, no input cost.
With debounce swift and bunny cheer,
The UI’s smooth, the bugs disappear!
Tab and Alt now play along,
In rabbit code, all keys belong.
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses the issue of stuck keys after a loss of focus by refining key debouncing and cleanup logic.

  • Added a helper method (ShouldProcessKeyEvents) to control key event processing during the debounce period.
  • Introduced state tracking for pressed keys and focus loss times in Menu.h/.cpp.
  • Updated the WM_KILLFOCUS hook in Hooks.cpp to exclusively use the new OnFocusLost() method.

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/Menu.h Added key event debounce method and related member variables.
src/Menu.cpp Integrated debouncing logic into key event processing and focus loss cleanup.
src/Hooks.cpp Refactored WM_KILLFOCUS hook to call OnFocusLost() exclusively.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/Menu.h (3)

7-8: Avoid using namespace in headers

Placing using namespace std::chrono; in a public header leaks the namespace into every translation unit that includes it and may cause symbol clashes.
Prefer fully-qualified names or a local using inside cpp files.


50-52: Document processing-window helper

ShouldProcessKeyEvents() is public but only meaningful internally.
Mark it private (or at most protected) to avoid accidental external use.


250-254: Potential false sharing on static chrono constant

FOCUS_LOSS_DEBOUNCE_MS is fine, but it could be inline constexpr to avoid multiple definitions across TUs (pre-C++17 ODR issue).
Minor, but keeps the header self-contained.

src/Menu.cpp (3)

2132-2134: Race-free access to debounce timestamp

ShouldProcessKeyEvents() assumes the caller already holds _inputEventMutex.
If it is ever invoked elsewhere without the lock, a data race on lastFocusLossTime appears.
Either:

bool ShouldProcessKeyEvents() const {
    std::shared_lock lock(_inputEventMutex);
    return (steady_clock::now() - lastFocusLossTime) >= FOCUS_LOSS_DEBOUNCE_MS;
}

or mark lastFocusLossTime as std::atomic<...> to guard future callers.


2158-2164: pressedKeys may bloat on key-repeat spam

When a key is auto-repeated, IsPressed() stays true, so each repeat still executes insert (no harm) but also allocates if the set is currently empty and you quickly press many distinct keys (e.g. typing).
If memory churn becomes measurable, consider pre-allocating:

pressedKeys.reserve(256);

Not critical but cheap to do.


2285-2289: Inline specifier keeps ODR simple

ShouldProcessKeyEvents() is tiny; marking it inline will prevent multiple-definition errors if the compiler inlines it into multiple TUs.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b33edcb and f8a9668.

📒 Files selected for processing (3)
  • src/Hooks.cpp (1 hunks)
  • src/Menu.cpp (4 hunks)
  • src/Menu.h (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build plugin and addons
🔇 Additional comments (2)
src/Hooks.cpp (1)

616-621: Consider covering WM_ACTIVATEAPP as well

WM_KILLFOCUS is not the only message signalling lost focus (e.g. fullscreen → Alt-Tab sends WM_ACTIVATEAPP/WA_INACTIVE without WM_KILLFOCUS).
To avoid missing cases, dispatch Menu::OnFocusLost() for WM_ACTIVATEAPP when wParam == FALSE.

src/Menu.cpp (1)

2166-2171: Debounce still forwards events to ImGui – double-check expectation

During the 100 ms debounce window you skip application hot-key logic but still pass key events to ImGui.
If the intent is to suppress all side-effects, forwardless would be safer; if UI consistency is desired, keep it.
Just confirm this matches design.

Comment thread src/Menu.cpp
Comment on lines +2248 to +2283
// Reset all key states to prevent stuck keys after Alt+Tab or focus loss
ImGuiIO& io = ImGui::GetIO();

// Log the number of tracked keys before clearing
size_t keyCount = pressedKeys.size();

// Reset all tracked pressed keys by sending key-up events
for (uint32_t key : pressedKeys) {
ImGuiKey imguiKey = VirtualKeyToImGuiKey(key);
if (imguiKey != ImGuiKey_None) {
io.AddKeyEvent(imguiKey, false); // Send key-up event
}
}
pressedKeys.clear();

// Reset modifier keys specifically (common culprits for sticking)
io.AddKeyEvent(ImGuiMod_Ctrl, false);
io.AddKeyEvent(ImGuiMod_Shift, false);
io.AddKeyEvent(ImGuiMod_Alt, false);

// Reset common problematic keys
io.AddKeyEvent(ImGuiKey_Tab, false);
io.AddKeyEvent(ImGuiKey_Escape, false);
io.AddKeyEvent(ImGuiKey_Space, false);
io.AddKeyEvent(ImGuiKey_Enter, false);

// Clear ImGui's internal navigation/active state to prevent infinite iteration
// This ensures that any active widget (input fields, dropdowns, etc.) loses focus
// and prevents navigation keys from getting stuck in infinite loops
ImGui::ClearActiveID();

// Record the focus loss time for debouncing
lastFocusLossTime = std::chrono::steady_clock::now();

logger::trace("Focus lost - cleared all key states ({} tracked keys), ImGui active ID, and event queue", keyCount);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Missing mouse button / wheel reset

OnFocusLost() releases keyboard keys but does not clear mouse buttons/wheel states.
If the user Alt-Tabs while holding a mouse button, ImGui may think it remains pressed.
Add:

for (int btn = 0; btn < ImGuiMouseButton_COUNT; ++btn)
    io.AddMouseButtonEvent(btn, false);
io.AddMouseWheelEvent(0,0);
🤖 Prompt for AI Agents
In src/Menu.cpp around lines 2248 to 2283, the OnFocusLost() function resets
keyboard keys but does not clear mouse button or wheel states, which can cause
ImGui to think mouse buttons remain pressed after focus loss. To fix this, add a
loop to send mouse button release events for all mouse buttons using
io.AddMouseButtonEvent with false, and reset the mouse wheel state by calling
io.AddMouseWheelEvent with zero values. Place this code after resetting keyboard
keys and before clearing ImGui's active ID.

@github-actions
Copy link
Copy Markdown

✅ A pre-release build is available for this PR:
Download

@alandtse
Copy link
Copy Markdown
Collaborator Author

Testers are saying this doesn't work. Need to rethink this.

@davo0411
Copy link
Copy Markdown
Collaborator

davo0411 commented Jun 18, 2025

Testers are saying this doesn't work. Need to rethink this.

the issue might be occuring on either of the Alt+tab presses, both before and after. If you're only adding a delay when re-tabbing back to the game, that first alt+tab to get out of the game might be causing the issue, preventing the fix here from helping.

Potentially some form of auto-discarding of inputs if a repeated input happens very fast over and over may help? This could bolster the resetting system you have already

@alandtse
Copy link
Copy Markdown
Collaborator Author

That's what debouncers do. I'll probably leave this here for others to work on but I'll move on.

@alandtse alandtse marked this pull request as draft June 19, 2025 03:29
@alandtse
Copy link
Copy Markdown
Collaborator Author

@soda3000 make sure you tag the closed issues in your PR. I'll close this draft out in favor of #1196

@alandtse alandtse closed this Jun 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tab + Alt results in Tab key being stuck. UI Bug - Alt+Tab cause loop over all component

4 participants