uttrflow / uttrflow/uttrflow-swift

⇧⌘V pastes instead of opening the clipboard when the hotkey registration is refused, and nothing says so

Open
#142 3 comments 0 reactions 1 assignee Claimed by @naveen-bhatt View on GitHub
area:clipboard bug confirmed P0
Dominant language
Swift
Stars
4
Forks
17
Avg merge
3h 32m
Merged PRs (30d)
277

Description

**P0 — ⇧⌘V does not open the clipboard. It pastes into whatever you were typing in.**

### What happens

Users press ⇧⌘V expecting the clipboard panel. The panel does not appear, and **text gets
pasted instead** — plain, unformatted. Reported by more than one person.

That second half is the tell, and it identifies the bug precisely.

### Why

⇧⌘V is claimed through Carbon, not through the event tap, and deliberately so
(`Docs/shortcuts.md`):

> `RegisterEventHotKey` **consumes** the combination, which a listen-only tap cannot do, and
> without that the clipboard panel would open *and* the app underneath would paste without
> formatting.

So the design depends on the registration succeeding. When it fails, the key is not consumed
and falls straight through to the frontmost application — where **⇧⌘V is "Paste and Match
Style"** in essentially every macOS text app. The user gets exactly half of the failure the doc
warned about: no panel, and an unformatted paste.

`CarbonHotkeyMonitor.start` throws when the combination is taken (`CarbonHotkeyMonitor.swift:101`):

```swift
guard status == noErr, let hotKey else {
// Fails when another app owns the combination; the shared handler stays.
hotkeySinks.withLock { $0[identifier] = nil }
throw .shortcutUnavailable
}
```

And `AppDelegate.startWatchingForClaimedShortcuts` swallows it
(`AppDelegate.swift:499`):

```swift
do {
try monitor.start(binding: binding)
} catch {
Self.log.error("\(action.rawValue) shortcut refused: \(error.userMessage)")
continue // <- silently gives up on this shortcut
}
```

An `os_log` line. Nothing in the interface. The Settings screen still shows ⇧⌘V as the
clipboard shortcut, so the app is displaying a binding it knows is not armed.

Contrast the dictation shortcut, which stores `shortcutFailure`, renders it, and retries when
the app is next activated (`AppDelegate.startWatchingForTheShortcut`). The claimed shortcuts get
none of that — the comment on the function even says so: *"a refusal is logged rather than shown
as a dictation failure."*

### Why registration fails for these users, and not on every machine

`RegisterEventHotKey` refuses a combination another process already owns. ⇧⌘V is heavily
contested — other clipboard managers, launchers, and text expanders all reach for it. Anybody
running one of those loses Uttrflow's clipboard silently.

Worth also checking self-collision: `startWatchingForClaimedShortcuts` runs again on every
shortcut change, stopping the old monitors and registering new ones. If a `stop()` does not
fully unregister before the re-registration runs, the app can lose the combination **to
itself**. That is a second, separate path to the same symptom and should be ruled in or out.

### Fix directions

Three things, roughly independent:

1. **Say it.** A refused shortcut must reach the user. The Settings row for that shortcut
should show it is not armed and why ("Another app is using ⇧⌘V"), the same way an
unavailable toggle already carries its reason. Displaying a binding that does nothing is the
worst of the three states.
2. **Retry.** The dictation shortcut re-arms on `applicationDidBecomeActive`. Claimed shortcuts
should too — the conflicting app may have quit.
3. **Investigate the self-collision** above before assuming every report is a third-party
conflict.

Not proposed: falling back to the event tap. The tap is listen-only and cannot consume the key,
so a fallback would produce the panel *and* the stray paste — which the doc already identifies
as the thing to avoid.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.