Conversation
The tap sat in the path of every keystroke on the system to act on a handful of them, and needed a watchdog to survive the OS disabling it — which it could never actually do, since a tap starved by Secure Input looks identical to one that is merely idle. RegisterEventHotKey removes CopyCat from the input path entirely, but it is all-or-nothing: once a chord is claimed the OS swallows it before any app sees it, with no way to decline. The chord in question is plain ⌘V, so it is armed only across the window where CopyCat would have acted anyway — a target app frontmost, an image on the clipboard, Accessibility granted — which reproduces the old conditional behavior. Every gate input is observable outside the keystroke path, so a hung handler can only delay CopyCat's own paste. The gate is sampled up to a poll interval before a keystroke lands, so a chord can fire after it closed. That case hands the keystroke back rather than dropping it, releasing every armed chord first: the re-posted event carries the same modifiers the hot key matches on, so posting it while the chord is still claimed would feed it straight back in. Also: - Menu header now tells apart "off", "needs Accessibility", and a chord another app owns, since none of those are fixed the same way. - Local paste and the Secure Input HID sensor share one cooldown, so a single keystroke seen by both paths types the path once. - Requires Swift 6.2 for isolated deinit in hot key teardown. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
macos-15 carries Xcode 16, whose Swift can't parse a 6.2 manifest, so the build broke the moment the package moved to 6.2. Runner images ship one Xcode major apiece, which makes the OS label the toolchain pin. Also run swift test: the suite covers the rule that decides when ⌘V is claimed system-wide, and a build-only job proves none of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v4 targets Node 20, which the runners now force onto Node 24 and warn about. v5 is the release that made that move deliberate; v7 is the current line and clears the same warning. Its one behavior change — refusing to check out fork PRs under pull_request_target and workflow_run — doesn't reach this workflow, which uses pull_request. Co-Authored-By: Claude Opus 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.
The event tap sat in the path of every keystroke on the system to act on a handful of them, and needed a watchdog to survive the OS disabling it — something it could never reliably do, since a tap starved by Secure Input looks identical to one that is merely idle.
RegisterEventHotKeytakes CopyCat out of the input path entirely, but it is all-or-nothing: once a chord is claimed the OS swallows it before any app sees it, with no way to decline and let the keystroke through. The chord in question is plain ⌘V, so holding it unconditionally would break paste system-wide.The arming gate
The chord is registered only across the window where CopyCat would have acted anyway — a target app frontmost, an image on the clipboard, Accessibility granted — which reproduces the old conditional behavior. Every gate input is observable outside the keystroke path (workspace activation, pasteboard change count, TCC trust), so a hung handler can only delay CopyCat’s own paste, never the user’s typing.
The gate is sampled up to one poll interval before a keystroke lands, so a chord can fire after it closed. That case hands the keystroke back rather than dropping it, releasing every armed chord first: the re-posted event carries the same modifiers the hot key matches on, so posting it while the chord is still claimed would feed it straight back in.
Also in here
isolated deinitin hot key teardown; CI moves to a runner image that carries a 6.2 toolchain and now runsswift test.Test plan
swift buildandswift testclean locally on Swift 6.2 (150 tests).🤖 Generated with Claude Code