perf(event-monitor): fix menu bar hover lag on high-frequency mouse events#1118
perf(event-monitor): fix menu bar hover lag on high-frequency mouse events#1118tisfeng merged 6 commits intotisfeng:devfrom
Conversation
|
Thanks your PR, we will review it later |
There was a problem hiding this comment.
Pull request overview
This PR reduces UI lag in the EventMonitor by avoiding expensive work on high-frequency mouse events and adding throttling around pop-button hover dismissal checks.
Changes:
- Short-circuit handling for
.mouseMovedand.scrollWheelto avoid per-eventfrontmostApplicationlookups. - Add a 100ms throttle for mouse-move handling, gated by pop-button visibility.
- Micro-optimize circle hit-testing by replacing
pow(x, 2)withx * xinGeometryHelper.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
Easydict/Swift/Utility/EventMonitor/Core/EventMonitor.swift |
Skips expensive operations for high-frequency events and throttles mouse-moved hover checks. |
Easydict/Swift/Utility/EventMonitor/Utilities/GeometryHelper.swift |
Replaces pow calls with multiplication for faster distance-squared computation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
The mouse movements on menubar were fine for me, even with the current release version. The movements were fine with this PR. Perhaps we can have someone who was experiencing issues in #747 wants to test? |
I see that the current release version works fine for you, but I submitted this PR to address issues I've observed in specific scenarios, particularly right after a fresh download and initial installation, or in some other edge cases. |
|
The code looks good, and in theory it can indeed be optimized for performance. |
Move the mouse-moved throttle logic into a reusable `ThrottleGate` utility and update `EventMonitor` to use synchronous allow/reset checks. Split the gate tests into a dedicated suite and register the new source files in the Xcode project.
|
I have fixed this small issue in #1118 (comment) , and refactor the code. Please review it. |
|
@tisfeng Your changes look good. I've pushed some additional fixes on top of them. I found that I also noticed that a few ObjC view classes ( I'm not sure these are the full root cause of the reported issues since some users see lag even without frequent text selection, so there might be more to it. I think we can merge this branch as an optimization, but we probably shouldn't close those issues yet until we can confirm the fix actually resolves them. There are also some smaller fixes in the commit — leaked keyDown monitor, missing cancel before rescheduling |
Fix CGEventTap RunLoop thread mismatch: - EventTapMonitor.start() was called from a background Swift concurrency Task, while stop() was called from the main thread. Both used CFRunLoopGetCurrent(), which returns different RunLoops on different threads. CFRunLoopRemoveSource in stop() silently failed, leaking a CFMachPort + CFRunLoopSource on every text selection cycle. - Fixed by using CFRunLoopGetMain() and storing the installed RunLoop reference so add/remove always target the same RunLoop. Fix NotificationCenter observer leaks in ObjC view classes: - EZQueryView: didChangeFontSize block captured self strongly without weakify, creating a retain cycle. Observer token was never stored. - EZBaseQueryViewController: didChangeFontSize block observer token was not stored. removeObserver:self in dealloc cannot remove block-based observers. - EZTitlebar: selector-based observers never removed (no dealloc). - EZTextView: selector-based observers never removed (no dealloc). Other minor fixes: - Fix leaked keyDown NSEvent monitor (return value was never stored) - Cancel previous pending timer before scheduling in delayDismissPopButton - Pass mouseLocation as parameter to avoid redundant NSEvent.mouseLocation calls - Remove duplicate frontmostApplication query in handleLeftMouseDown - Add DEBUG-only slow event handler logging (>16ms warning)
1b8a3fc to
acb7bce
Compare
There was a problem hiding this comment.
Pull request overview
Improves menu bar hover responsiveness by reducing work performed on high-frequency input events in the event-monitoring pipeline, and adds a small throttling utility with unit tests.
Changes:
- Skip
frontmostApplicationlookup for.mouseMoved/.scrollWheel, and throttle mouse-move handling while the pop button is visible. - Introduce
ThrottleGate+ unit tests to support synchronous throttling. - Minor robustness/perf improvements: fix observer lifetime handling in ObjC views/controllers, optimize geometry distance math, and make
EventTapMonitorrun-loop installation consistent.
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 |
|---|---|
| EasydictTests/Utility/ThrottleGateTests.swift | Adds unit coverage for the new throttle gate behavior. |
| Easydict/Swift/Utility/ThrottleGate.swift | Introduces a small synchronous throttle primitive used by the event monitor. |
| Easydict/Swift/Utility/EventMonitor/Core/EventMonitor.swift | Early-returns on high-frequency events and throttles .mouseMoved; fixes local monitor removal; resets throttle across visibility sessions. |
| Easydict/Swift/Utility/EventMonitor/Engine/EventTapMonitor.swift | Attempts to make run-loop add/remove consistent by targeting the main run loop. |
| Easydict/Swift/Utility/EventMonitor/Utilities/GeometryHelper.swift | Replaces pow calls with multiplication for faster distance-squared math. |
| Easydict/objc/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m | Stores/removes the block-based font-size observer token to avoid leaks. |
| Easydict/objc/ViewController/View/QueryView/EZQueryView.m | Stores/removes the block-based font-size observer token and avoids strong capture. |
| Easydict/objc/ViewController/View/Titlebar/EZTitlebar.m | Removes notification observers in dealloc to prevent callbacks after deallocation. |
| Easydict/objc/ViewController/View/TextView/EZTextView.m | Removes notification observers in dealloc to prevent callbacks after deallocation. |
| Easydict.xcodeproj/project.pbxproj | Registers new Swift source and test files in the Xcode project. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add missing `mm_strongify(self)` to the query and result view callbacks that were still referencing `self` directly after `mm_weakify(self)`. This restores the intended weak/strong pattern and prevents retain cycles between the views and their block handlers.
Good, I reviewed the code, looks fine |
Summary
Fix #747, #1102
handleMonitorEventcallsNSWorkspace.shared.frontmostApplicationon every event includingmouseMoved(~60–100 Hz). The original ObjC implementation only fetched it on demand inside specific handlers likeleftMouseDownandleftMouseUp.Changes
mouseMovedandscrollWheelbefore thefrontmostApplicationlookupisMouseInPopButtonExpandedFrame()now only runs when the pop button is visiblepow(x, 2)withx * xinGeometryHelperBefore / After
Before: every mouse-move event triggers a
frontmostApplicationlookup and pop-button frame check, even when the button is hidden.After: high-frequency events skip the lookup entirely. The frame check runs at most 10 times per second and only while the button is on screen.
Test plan