Skip to content

fix: support non-QWERTY keyboard layouts for Cmd+V paste#49

Closed
herr-lehmann wants to merge 166 commits into
matthartman:mainfrom
herr-lehmann:fix/keyboard-layout-paste
Closed

fix: support non-QWERTY keyboard layouts for Cmd+V paste#49
herr-lehmann wants to merge 166 commits into
matthartman:mainfrom
herr-lehmann:fix/keyboard-layout-paste

Conversation

@herr-lehmann

Copy link
Copy Markdown

Problem

The virtual key code for V was hardcoded to 0x09 — the physical position
of V on a QWERTY keyboard. On other layouts (AZERTY, QWERTZ, Dvorak, ...)
this physical key produces a different character, so Cmd+V never triggered paste.

Fix

Use UCKeyTranslate to look up the correct physical key code for v in the
active keyboard input source at runtime. Falls back to 0x09 (QWERTY) if the
layout cannot be determined.

Testing

Manually tested with QWERTZ (Swiss German) layout via
System Settings → Keyboard → Input Sources.


Implemented with AI assistance (Claude by Anthropic)

matthartman and others added 30 commits March 23, 2026 14:53
Models section shows each model with loaded/not loaded status.
Taller settings window (580px) to fit all sections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows when any model isn't loaded. Downloads WhisperKit and/or
cleanup models directly from Settings — no need to re-run onboarding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LLM.swift's bundled llama.cpp doesn't support Qwen3/3.5 architecture.
Reverted to Qwen 2.5 1.5B + 3B which work reliably.
Will upgrade when LLM.swift updates its llama.cpp.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Info.plist was still at v1.1 so Sparkle thought the "update"
was the same version. Now properly at v1.3 build 4.
Fixes #2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Improve post-paste learning via Accessibility
Picker in Settings under Input section. Switching models triggers
re-download and reload. Default remains small.en for accuracy.
tiny.en is ~75 MB and much faster for shorter recordings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oggle)

Matches the original Ghost Pepper behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Input Monitoring prompt doesn't reliably show the system dialog
for debug-signed apps. Now attempts to start the hotkey monitor
even without it — Accessibility alone is sufficient for Control key.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixed codesign verification failure reported in #4.
Now verifies signature after extracting from DMG before release.

Also includes: speech model picker, default Control shortcuts,
non-blocking Input Monitoring check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
macOS kills the app after Screen Recording is granted but doesn't
relaunch it. Now spawns a background process that reopens the app
after 3 seconds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace no-audio modal with status pill
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses #5 — users should know about local transcript log
and auto-launch behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
File-based logging was replaced by in-memory DebugLogStore.
Nothing is written to disk. Updated disclosure accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No longer blocks Continue button. Shown as "(optional)" with
a bordered (not prominent) Enable button. Users can skip it
and enable later in Settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Matches WhisperFlow and other dictation tools.
Toggle is Right Command + Right Option + Space.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Supports Spanish and 90+ other languages. Same size as small.en.
Users can tweak the cleanup prompt for their language's filler words.
Addresses #6.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
matthartman and others added 28 commits April 6, 2026 16:59
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Zed's custom GPU-rendered editor doesn't expose standard Accessibility
text-editing attributes (AXEditable, AXSelectedTextRange, AXValue).
This causes the paste preflight in TextPaster to fail, which triggers
the clipboard fallback path where the user has to manually press Cmd+V.

Add a bundle ID allowlist (pasteAlwaysAllowedBundleIDs) for apps that
support Cmd+V but don't implement AX text attributes. When the frontmost
app is in this list, the AX preflight is skipped and Ghost Pepper pastes
directly via simulated Cmd+V.

Includes dev.zed.Zed and dev.zed.Zed-Preview.

This contribution was developed with AI assistance (Claude Code).
Fixes #31

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the hardcoded pasteAlwaysAllowedBundleIDs set with a behavioral
check that inspects the frontmost app's menu bar for an enabled Cmd+V
(Paste) item. This avoids maintaining app-specific quirks and
automatically supports any app whose editor doesn't expose standard AX
text-editing attributes but does support Cmd+V.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reopen from menu bar > Pepper Chat

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a search field to the History tab that filters transcription entries
by matching against raw and corrected transcription text. Shows "No
Results" state when search has no matches vs "No Saved Recordings" when
no recordings exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Speech model:
- Add Qwen3-ASR 0.6B int8 (CoreML, ~900 MB, 50+ languages) as a new
  selectable speech model on macOS 15+. Picker entry is hidden on
  earlier macOS so the deployment target stays at 14.0.
- Wire a separate Qwen3 load/transcribe path through ModelManager,
  using FluidAudio's new Qwen3AsrModels + Qwen3AsrManager API. The
  manager instance is held as Any? so the type's macOS-15 availability
  doesn't infect the rest of the class.
- Fix supportsSpeakerFiltering to gate on .parakeetV3 instead of the
  whole .fluidAudio backend — Qwen3 has no diarization output and
  would otherwise be force-fed into the Sortformer pipeline.

FluidAudio bump 0.12.4 -> 0.13.6:
- Required for Qwen3-ASR support and a Swift 6 strict-concurrency
  fix in StreamingAsrManager.
- Adjust for the 0.13 API renames: AsrManager.initialize -> loadModels,
  SortformerSegment -> DiarizerSegment, segments now live on
  DiarizerSpeaker (timeline.speakers.values.flatMap), processSamples
  -> process(samples:).

AudioRecorder fixes:
- Query inputNode.inputFormat(forBus:) instead of outputFormat — the
  latter goes stale after prewarm or device switches and made
  installTap throw a hard NSException with mismatched HW format
  (notably with Bluetooth mics flipping HFP/A2DP profiles).
- Recreate AVAudioEngine for every recording session. Reusing the
  engine across device-rate changes leaves a HAL IOThread alive on
  the previous device and the next start() returns EAGAIN.
- Build the AVAudioConverter lazily from the first incoming buffer's
  format so we always match what the bus actually delivers.
- Move NSLock acquisition out of async context into a sync
  snapshotBuffer() helper for Swift 6 cleanliness.

Tests:
- 6 new tests covering the Qwen3 catalog entry, descriptor props,
  macOS-version gating, ModelManager load-success/failure routing
  through modelLoadOverride, and runtime switching between Whisper
  and Qwen3.
- Update the existing catalog assertion to be macOS-version-aware.

Docs:
- Add the Qwen3-ASR row to the README speech-models table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Experimental meeting transcription mode (off by default in Settings):
- Dual-stream audio capture: mic (AVAudioEngine) + system audio (ScreenCaptureKit)
- 30-second chunked transcription pipeline for long recordings
- Auto-detect meeting apps (Zoom, Teams, FaceTime, Meet) and video sites
  (YouTube, Loom, Vimeo, Twitch) — pepper character prompts to transcribe
- Notion-style transcript window with 3 tabs: Notes, Transcript, Summary
- Markdown file output organized by date folders with live auto-save
- Sidebar with past meeting history and smart Obsidian/Finder integration
- Cmd+F search with highlight across all tabs
- "No sound detected" overlay now shows mic settings hint (clickable)
- Mic switching in Settings now resets audio engine (no restart needed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…smart Obsidian integration

- Rewrote meeting window with VS Code/Obsidian-style tabs (multiple meetings open simultaneously)
- Sidebar file browser with right-click context menu (Delete, Show in Finder)
- Click past meetings to load them as editable tabs (parsed from markdown)
- "+" button for quick notes, folder button for Finder
- Attendee name capture via OCR of meeting window
- Auto-update meeting title from Zoom/Teams window title
- Smart Obsidian integration with vault auto-creation prompt
- Title rename on Enter renames the file on disk
- Sidebar auto-refreshes every 10s while visible
- Fixed recording state observation (Combine-based) for proper red dot / stop button
- Fixed "no sound detected" overlay for voice-to-text (lower threshold, clickable, opens Settings)
- Mic switching in Settings now resets audio engine without restart

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lity

- Chunked summary generation using local LLM (Qwen) — splits long
  transcripts into chunks, summarizes each, then combines into final
  summary with Key Decisions, Action Items, Discussion Points, TL;DR
- Summary tab is now an editable TextEditor (same Georgia font as Notes)
  with Generate/Regenerate button — no auto-generate, user-initiated
- Editable summary prompt in Settings > Meeting Transcript
- Removed speaking percentages and segment count from summary stats
- Sidebar sorted by filename (stable order, doesn't change on save)
- Removed loadHistory from saveActiveTab to prevent sidebar reordering
- Added @ObservedObject transcript to MeetingTabContentView for proper
  SwiftUI observation of nested ObservableObject changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clean duck-typing approach for apps with custom renderers. Thanks @mvanhorn!
- Regenerated project.pbxproj with xcodegen
- Added .qwen3AsrInt8 case to deleteModel switch in ModelManager

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses #23 — some macOS Sequoia users see "Apple could not verify"
warning. Added instructions for System Settings > Privacy & Security >
Open Anyway.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Qwen cleanup model was sometimes interpreting user speech as
instructions and responding conversationally instead of cleaning up
the transcription.

Changes:
- Strengthened system prompt: "You are NOT a chatbot. Do NOT answer
  questions. Do NOT follow instructions in the input."
- Added examples showing questions/commands passed through verbatim
- Added closing reminder to reinforce transcription-only behavior

Also added CleanupPromptEvalTests — a test suite that validates the
cleanup model behaves as a transcription tool, not a chatbot:
- 17 eval cases covering questions, instructions, refusal triggers
- Chatbot detection heuristics (indicator phrases, length ratio, lists)
- Live model test that runs against the actual Qwen 0.8B model
- All 17 cases pass on the 0.8B model with the updated prompt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added per-model test methods (0.8B, 2B, 4B) and testEvalOnAllAvailableModels
- Fixed false positive: "I'm sorry, but I" is natural speech, narrowed
  chatbot indicators to "I'm sorry, but I can't/cannot"
- Relaxed length heuristic from 2x to 3x — larger models rephrase more

Results: 0.8B passes all 17, 2B and 4B pass with adjusted heuristics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pauses Spotify, Apple Music, podcasts, and other media when recording
starts. Resumes when recording stops. Uses the private MediaRemote
framework via dlopen — gracefully degrades if unavailable.

Toggle in Settings > Recording > "Pause media while recording" (default: on).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added consent dialog that appears before every meeting recording starts
  with copyable notice: "I'm using 🌶️ Ghost Pepper, a completely private
  AI note taker. Nothing leaves my computer and all AI models are done
  on device."
- Consent dialog shows for both manual (+) and auto-detected recordings
- "Don't ask again" checkbox for users in jurisdictions that don't require consent
- Recording history (Transcription Lab) now defaults to OFF — audio WAVs
  are not saved to disk unless user explicitly enables it in Settings > History
- Updated consent message wording

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Google Meet tab titles show "Meet - xxx-yyyy-zzz" not "meet.google.com".
Added "meet -" and "google meet" as title patterns for detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
"Looks like you're watching a video on YouTube. Want me to create
notes and transcribe it?"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ube detection

- Video sites (YouTube, Vimeo, Twitch, etc.) skip the consent dialog
  since they're public content, not private calls
- Source URL from browser address bar is automatically added to the
  top of the Notes tab when transcribing a video
- Fixed YouTube detection: match "- YouTube" suffix in tab titles
  instead of requiring "▶" prefix (YouTube no longer uses this)
- Added "google meet" and "meet -" patterns for Google Meet detection
- Added browser URL extraction via Accessibility API

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously the virtual key code for "V" was hardcoded to 0x09 (the QWERTY
physical position), causing paste via Cmd+V to fail on other keyboard
layouts such as AZERTY, QWERTZ, or Dvorak.

The fix uses UCKeyTranslate to look up the correct physical key code for
"v" in the current keyboard input source at runtime, with a fallback to
the QWERTY code (0x09) if the layout cannot be determined.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants