⚡ Harden input monitor lifecycle + improve shutdown#1094
Merged
Conversation
This was
linked to
issues
May 8, 2026
Closed
Contributor
|
🚧 Development Build Finished
|
There was a problem hiding this comment.
Pull request overview
This PR moves global CoreGraphics event taps onto a dedicated run loop thread to decouple input monitoring from the app’s main run loop, and hardens shutdown/termination so Loop can reliably stop intercepting input and exit even if stash restoration stalls. It also adjusts stash frame computation to refresh resolved window state, addressing the reported “preserve size” regression for stashed windows.
Changes:
- Introduces a dedicated
EventTapThreadrun loop and routes event-tap sources through it (instead of the main run loop). - Adds explicit shutdown paths for
LoopManagerandWindowDragManager, and bounds stash shutdown during app termination via a timeout. - Makes stash “revealed frame” computation async and refreshes resolved state before computing target frames.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| Loop/Window Management/Window/Window.swift | Runs animated frame changes on @MainActor and starts window transform animations without an extra task wrapper. |
| Loop/Window Management/Window Manipulation/WindowTransformAnimation.swift | Marks the animation class @MainActor to keep UI animation interactions on the main actor. |
| Loop/Utilities/Event Monitoring/EventTapThread.swift | Adds a dedicated thread + run loop for global event taps. |
| Loop/Utilities/Event Monitoring/BaseEventTapMonitor.swift | Attaches event taps to the dedicated run loop and centralizes teardown logic. |
| Loop/Stashing/StashManager.swift | Makes revealed-frame access async and updates call sites to await it. |
| Loop/Stashing/StashedWindowInfo.swift | Refreshes resolved state before computing revealed frames to better preserve size/geometry. |
| Loop/Core/WindowDragManager.swift | Adds shutdown() and ensures listeners are reset before re-adding. |
| Loop/Core/Observers/MouseInteractionObserver.swift | Ensures start() clears any prior monitors/state via stop(). |
| Loop/Core/LoopManager.swift | Adds atomic snapshot helpers for event-tap callbacks and improves open/close/shutdown lifecycle handling. |
| Loop/App/AppDelegate.swift | Stops input monitors before stash restoration and bounds stash shutdown during termination with a timeout helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
5b3fa3a to
92bd278
Compare
Contributor
|
🚧 Development Build Finished
|
Contributor
|
🚧 Development Build Finished
|
1aaedf3 to
aa1f04c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR moves global event monitoring off the main run loop and onto a dedicated event-tap run loop. This keeps event delivery and event tap teardown from depending on Loop’s main run loop, which should reduce the chance that a main-thread stall leaves Loop intercepting input or prevents the user from quitting/interacting with the app.
It also hardens Loop’s shutdown path. Previously, Loop returned
.terminateLaterand waited for all stashed windows to restore before allowing termination to continue. If that restore path stalled, Loop could remain stuck in termination indefinitely. This was worse if input event taps were still active. Loop now stopsLoopManagerandWindowDragManagerinput monitors before waiting on stash restoration, and stash shutdown is bounded so termination can continue.A few small thread-safety helpers were added as well:
OneShotContinuationprevents an async continuation from being resumed more than once.isLoopActiveAtomicandhasParentCycleActionAtomicprovide synchronous, thread-safe snapshots for event-tap callbacks. These event-tap reads cannot use actors directly because CoreGraphics event tap callbacks are synchronous (for obvious reasons)Also fixes #1077 where stashed windows would not respect a preserved window size, caused by earlier concurrency fixes :)