Fix menu click sometimes pasting the newest clipping instead of the clicked one#7
Open
MiMoHo wants to merge 1 commit into
Open
Fix menu click sometimes pasting the newest clipping instead of the clicked one#7MiMoHo wants to merge 1 commit into
MiMoHo wants to merge 1 commit into
Conversation
…licked one The pollPB: timer runs in NSRunLoopCommonModes, so a clipboard change can be noticed while the status menu is open (deliberate, for Universal Clipboard). That triggers updateMenu, which removes every clipping item and inserts new NSMenuItem objects. If the rebuild lands between the user's click and the action dispatch, the clicked item is no longer in the menu, [sender menu] is nil, and [[sender menu] indexOfItem:sender] messages nil and yields 0 -- pasting stack position 0, the most recently copied clipping, instead of the entry the user clicked. With the 1-second poll interval this commonly happens when the user copies something and opens the menu right away. - processMenuClippingSelection: bail out when the sender is orphaned or its index can't be resolved, instead of pasting the wrong clipping. - pasteIndexAndUpdate: now returns whether a clipping was placed on the pasteboard, so no Cmd-V is faked when nothing was pasted (previously that re-pasted whatever was already on the pasteboard). Also bounds-check the search mapping, which could throw NSRangeException. - searchWindowItemSelected: use clickedRow for double-clicks so a selection change between click and action can't redirect the paste to row 0, and bounds-check the search mapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Symptom
Sometimes, clicking an older entry in the status menu pastes the most recently copied clipping instead of the one that was clicked. It is intermittent and more likely the further down the list you click.
Root cause
The chain is fully deterministic once the timing lines up:
pollPB:runs on a 1-second timer scheduled inNSRunLoopCommonModes— deliberately, so Universal Clipboard arrivals are noticed while the menu is open (comment inawakeFromNib).When it notices a clipboard change,
addClipping:firesupdateMenu→updateMenuContaining:, which removes every clipping item and inserts brand-newNSMenuItemobjects (also scheduled inNSRunLoopCommonModes).NSMenu dispatches the clicked item's action slightly after mouse-up (the highlight blink). If the rebuild from step 2 lands in that window, the clicked item has been removed from the menu, so in
processMenuClippingSelection::[sender menu]isnil, messaging nil yields0, andpasteIndexAndUpdate:0pastes stack position 0 — the newest clipping. WithmenuSelectionPastesdefaulting to YES, the fake Cmd-V then pastes it into the frontmost app.No external clipboard source is needed: with the 1 s poll interval, the user's own Cmd-C is often detected only after the menu is already open (copy → open menu → browse → click lands right around the poll tick).
Standalone proof of the mechanism (AppKit, compiled with
clang -fno-objc-arc -framework AppKit):(Test source is a ~50-line program that builds a menu, retains item 7 as
sender, replaces all items the wayupdateMenuContaining:does, and evaluates the exact expression fromprocessMenuClippingSelection:.)Fix
processMenuClippingSelection:— resolve the index defensively; if the clicked item is orphaned (menunil) or its index can't be resolved, do nothing rather than paste the wrong clipping. The menu is current again on the next click.pasteIndexAndUpdate:— now returns whether a clipping was actually placed on the pasteboard. Previously, when it silently found no content, the caller still faked Cmd-V, re-pasting whatever was already on the pasteboard (same visible symptom through a second hole). Also bounds-checks the search mapping, which could throwNSRangeExceptionwhen the list changed between menu build and click.searchWindowItemSelected:— for double-clicks, useclickedRowinstead ofselectedRow, so a selection reset between click and action (e.g.updateSearchResultsre-selecting row 0 when the search field action fires) can't redirect the paste to the newest entry; double-clicks below the last row are ignored; the search mapping is bounds-checked.Verification
clang -fsyntax-onlydiagnostics are identical tomaster(16 pre-existing warnings, nothing new). I don't have Xcode on this machine, so no functional build — the mechanism itself is proven by the standalone test above, and CI should build this like the other PRs.menuWillOpen:/menuDidClose:are already implemented) or giving items identity viarepresentedObject. Happy to follow up with either if you want.🤖 Generated with Claude Code