Add Layout Hotkeys extension - #30140
Conversation
Switch macOS keyboard layouts directly with a hotkey instead of cycling through them, and rewrite text that was typed with the wrong layout active. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Congratulations on your new Raycast extension! 🚀 We're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 15 business days. Once the PR is approved and merged, the extension will be available on our Store. |
Greptile SummaryThe PR adds a macOS extension for direct keyboard-layout switching and runtime key-layout-based text conversion.
Confidence Score: 4/5The PR does not yet appear safe to merge because whole-field conversion can overwrite a different application's focused field than the one whose contents were converted. The picker separates field reading from replacement, while the apply path discards the original target identity and resolves the currently frontmost non-Raycast field again before selecting all and pasting. Files Needing Attention: extensions/layout-hotkeys/src/lib/convert.ts and extensions/layout-hotkeys/swift/Sources/FocusedField.swift Important Files Changed
Prompt To Fix All With AI### Issue 1
extensions/layout-hotkeys/src/lib/convert.ts:116-119
**Whole-Field Replacement Changes Target**
When another application becomes frontmost while the conversion picker is open, `selectAllInField()` re-resolves that application's focused field instead of retaining the field read at picker load, causing the converted text to overwrite an unrelated field or fail when the new field is not editable.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (3): Last reviewed commit: "Spell out how this differs from Keyboard..." | Re-trigger Greptile |
… app Convert Selection is a view command, so its window stays frontmost while the command runs and waitForRaycastToYieldFocus can never resolve. Both readFocusedField and selectAllInFocusedField therefore timed out and queried Raycast's own search field, so the fallback that converts the whole focused field failed with "No text field is focused." there. It worked only from the no-view hotkeys, which dismiss the window first. Resolve the target through the frontmost window that Raycast does not own, keeping the existing frontmostApplication fast path, and drop the wait from this path: Accessibility reads and writes do not need the target app to be active. Hand focus back before rewriting the field so the selection and the paste act on the same window. Also treat a selection of nothing but whitespace as an explicit selection rather than as nothing selected, so it can no longer escalate to a whole-field rewrite, and type the preference helpers with the generated Preferences so renaming a preference fails the build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Similar extensions already in the StoreAutomated first pass. Overlap is not a blocker on its own, but the README should make the difference clear.
|
The store duplicate check flagged the overlap. Name the existing extension in the README and say plainly what it does and does not do, so the difference does not have to be inferred from the command list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thanks for the pointer — I've updated the README to name Keyboard Layout Switcher and spell out the difference, in 52e38fc. For the record on the overlap itself: Keyboard Layout Switcher has two commands, a picker listing your layouts and "Toggle Next Layout". Neither of the things this extension is for is among them.
There's also no external dependency: it drives the separately installed The genuine overlap is Switch Input Source and Cycle Input Source, which do roughly what its two commands do. Those are here so the four hotkeys aren't the only way in, and I'm happy to drop either if you'd rather keep the Store tidy. |
| await closeMainWindow(); | ||
|
|
||
| try { | ||
| await selectAllInField(); |
There was a problem hiding this comment.
Whole-Field Replacement Changes Target
When another application becomes frontmost while the conversion picker is open, selectAllInField() re-resolves that application's focused field instead of retaining the field read at picker load, causing the converted text to overwrite an unrelated field or fail when the new field is not editable.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/layout-hotkeys/src/lib/convert.ts
Line: 116-119
Comment:
**Whole-Field Replacement Changes Target**
When another application becomes frontmost while the conversion picker is open, `selectAllInField()` re-resolves that application's focused field instead of retaining the field read at picker load, causing the converted text to overwrite an unrelated field or fail when the new field is not editable.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Description
Switch macOS keyboard layouts directly with a hotkey instead of cycling through them, and rewrite text that was typed with the wrong layout active.
macOS only offers "select next input source", so reaching a specific layout out of four means pressing the switch key three times. This gives four commands you can bind independent global hotkeys to, each landing directly on the layout assigned to it.
Commands
Notes for review
No hardcoded layout tables. Text conversion derives its mapping at runtime from each layout's own key tables via
UCKeyTranslateandkTISPropertyUnicodeKeyLayoutData. For each character it resolves which physical key and modifiers produced it under the detected layout, then asks the target layout what that key produces. Any pair of enabled layouts therefore works, including custom.keylayoutbundles built with Ukelele, with nothing to maintain per language. Round-trips (A → B → A) return the original exactly, including Option-key diacritics such as Polishą ć ż.Permissions. The switching commands need none — no Accessibility, no Input Monitoring, no synthesized keystrokes. The Convert Selection commands read the frontmost app's selection, which requires Raycast's own Accessibility permission.
Whole-field fallback. With nothing selected, the convert commands read the focused field through the Accessibility API rather than synthesizing ⌘A, since a synthetic keystroke would land on Raycast while it briefly holds focus. Because "the focused field" can be an entire document, anything over 200 characters asks for confirmation first, and a
When Nothing Is Selectedpreference disables the fallback entirely.Focus handling. A Raycast command runs with Raycast briefly frontmost. With macOS's "Automatically switch to a document's input source" enabled, switching then would be undone once focus returns, so the Swift side waits up to 400 ms for Raycast to stop being frontmost before calling
TISSelectInputSource. The hotkey commands deliberately do not dismiss the Raycast window, so they also work while typing in Raycast's own search field.Swift is bundled as source and compiled by
extensions-swift-tools; no prebuilt binary is committed.🤖 Generated with Claude Code