Skip to content

Fix VoiceOver: hide recording overlays and pass through system hotkeys#590

Open
Alshekhi wants to merge 3 commits into
altic-dev:mainfrom
Alshekhi:voiceover-fixes
Open

Fix VoiceOver: hide recording overlays and pass through system hotkeys#590
Alshekhi wants to merge 3 commits into
altic-dev:mainfrom
Alshekhi:voiceover-fixes

Conversation

@Alshekhi

@Alshekhi Alshekhi commented Jul 11, 2026

Copy link
Copy Markdown

Description

Two accessibility fixes for VoiceOver users:

  • GlobalHotkeyManager — a fast-path in the CGEvent tap that passes the macOS system shortcuts Cmd+Tab and Cmd+Space straight through, so the tap doesn't add latency to app/space switching while the dictation hotkey is active. This is especially noticeable with VoiceOver, whose own event handling is latency-sensitive. The fast-path is skipped when the user has bound one of Fluid's own shortcuts to the same chord, and it preserves the modifier-only bookkeeping so a system chord pressed mid-hold isn't mistaken for a clean tap.
  • BottomOverlayViewsetAccessibilityElement(false) + setAccessibilityRole(.unknown) on all four recording-overlay panels, and .accessibilityHidden(true) on the SwiftUI body, so VoiceOver doesn't announce the overlays or shift focus to them.

Both match behavior the repo's CLAUDE.md already documents as intended (overlay panels must hide from VoiceOver; the event tap must pass system shortcuts through immediately).

Type of Change

  • 🐞 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 🧹 Chore
  • 📝 Documentation update

Related Issue or Discussion

Addresses #401 (VoiceOver: dictation overlay steals focus and Cmd+Tab becomes sluggish).

This PR fixes the two root causes in this repo:

  1. BottomOverlayView panels being discoverable by VoiceOver (overlay focus stealing).
  2. Cmd+Tab / Cmd+Space sluggishness from the CGEvent tap.

The third root cause noted in #401DynamicNotchKit's DynamicNotchPanel overriding canBecomeKey/canBecomeMain — lives in the separate DynamicNotchKit dependency and is out of scope here; it only affects the non-default notch overlay and would need a fix in that repo. This PR covers the default bottom overlay path, so #401 is intentionally addressed, not auto-closed.

Testing

  • Tested on Intel Mac
  • Tested on Apple Silicon Mac
  • Tested on macOS version: 26.5.2 (Apple Silicon)
  • Ran linter locally: swiftlint --strict --config .swiftlint.yml Sources
  • Ran formatter locally: swiftformat --config .swiftformat Sources
  • Ran tests locally:

Manually verified: pressing the dictation hotkey with VoiceOver active no longer delays Cmd+Tab / Cmd+Space; the recording overlays are no longer announced by or focusable in VoiceOver.

Screenshots / Video

  • No UI/visual changes; screenshots/video are not applicable.

The overlay change only sets accessibility attributes (setAccessibilityElement, setAccessibilityRole, .accessibilityHidden) — it does not alter rendering in any way.

Notes

Tested locally against the current main. The hotkey fast-path is limited to Cmd+Tab and Cmd+Space (both layout-stable hardware keycodes) to avoid the ANSI/ISO ambiguity of the backtick key.

- GlobalHotkeyManager: fast-path pass-through for Cmd+Tab/Space/` in the
  event tap so VoiceOver isn't delayed on the dictation hotkey.
- BottomOverlayView: setAccessibilityElement(false)/role .unknown on all
  overlay panels and .accessibilityHidden(true) on the body so VoiceOver
  ignores the overlays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown

The PR Policy check is blocking this PR because required template information is missing.

Please update the PR description with:

  • Related Issue or Discussion

Visual files detected:

  • Sources/Fluid/Views/BottomOverlayView.swift

Screenshots or video are required for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes. If this PR has no visual changes, check the no-visual-change box in the template.

If this remains incomplete for 48 hours after opening, the PR may be closed.

@github-actions github-actions Bot added needs PR template Pull request is missing required template content. needs screenshots Pull request needs screenshot or video evidence. labels Jul 11, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ea11f2858

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +599 to +603
if flags.contains(.maskCommand) {
let kc = UInt16(event.getIntegerValueField(.keyboardEventKeycode))
// Tab(48), Space(49), Backtick(50 — Cmd+` for window cycling)
if kc == 48 || kc == 49 || kc == 50 {
return Unmanaged.passUnretained(event)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark Cmd system shortcuts as modifier-only interruptions

When the configured Fluid shortcut is a modifier-only Command key, this early return runs before the .keyDown branch can call markOtherInputDuringModifierOnly(). Pressing Cmd+Tab, Cmd+Space, or Cmd+while holding Command therefore leavesotherKeyPressedDuringModifierfalse; on the later CommandflagsChangedrelease,handleModifierOnlyShortcutFlagsChanged` treats the sequence as a clean tap and can toggle/start dictation instead of ignoring the combo. Please record the interruption before passing these keys through, or move the pass-through after that bookkeeping.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jul 11, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes two VoiceOver accessibility regressions: recording overlays being discoverable by VoiceOver, and Cmd+Tab / Cmd+Space gaining latency from the CGEvent tap while a dictation shortcut is active.

  • GlobalHotkeyManager: adds a modifierFlags(from:) refactor (eliminating inline duplication), an eventMatchesAnyConfiguredShortcut helper that gates the fast-path against every user-configured binding, and a fast-path that returns layout-stable keycodes 48 (Tab) and 49 (Space) straight through when Command is held — skipping all further processing only when no Fluid shortcut matches the chord. Keycode 50 is explicitly excluded due to ANSI/ISO ambiguity.
  • BottomOverlayView: all four overlay panels receive setAccessibilityElement(false) + setAccessibilityRole(.unknown) at the AppKit layer; the SwiftUI body gains .accessibilityHidden(true). No rendering changes.

Confidence Score: 5/5

Safe to merge — the changes are narrowly scoped to accessibility attributes and a latency fast-path that is correctly gated against all user-configured shortcuts.

Both previously flagged issues (silent shortcut swallowing and ISO keycode mismatch) are fully resolved. The fast-path consults every configured shortcut collection before returning early, and keycode 50 is deliberately excluded with an explanatory comment. The AppKit + SwiftUI accessibility changes are additive, touch no rendering logic, and cover all four overlay panels consistently.

No files require special attention.

Reviews (3): Last reviewed commit: "fix: include prompt-mode shortcut in fas..." | Re-trigger Greptile

Comment thread Sources/Fluid/Services/GlobalHotkeyManager.swift
Comment thread Sources/Fluid/Services/GlobalHotkeyManager.swift Outdated
Addresses automated review feedback on the system-shortcut fast-path:

- Skip the fast-path when the chord matches a user-configured Fluid
  shortcut (cancel, paste-last, prompt, command, rewrite, primary), so
  real bindings on Cmd+Tab/Space are never silently swallowed.
- Call markOtherInputDuringModifierOnly() before passing the event
  through, so a system chord pressed during a modifier-only shortcut
  isn't mistaken for a clean tap on release.
- Drop keycode 50 (Cmd+`): it is ANSI-only and maps to a different
  physical key on ISO layouts. Keep only Tab(48)/Space(49), which are
  layout-stable.
- Extract flag->modifier mapping into modifierFlags(from:) so the tap
  and matcher share one source of truth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66b909a60c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +559 to +562
if self.commandModeShortcutEnabled,
let commandModeShortcut = self.commandModeShortcut,
commandModeShortcut.matches(keyCode: keyCode, modifiers: modifiers)
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include prompt-mode shortcuts in fast-path guard

When the Secondary Dictation/Prompt Mode shortcut is assigned to Cmd+Space or Cmd+Tab, this helper still returns false because it checks prompt assignments and then jumps straight to command mode without testing promptModeShortcut. The new fast path in handleKeyEvent then returns before handlePromptModeKeyDown can run, so that configured Fluid binding is ignored and the macOS system shortcut fires instead. Please include the enabled prompt-mode shortcut in this guard like the other configured shortcuts.

Useful? React with 👍 / 👎.

@github-actions github-actions Bot removed needs PR template Pull request is missing required template content. needs screenshots Pull request needs screenshot or video evidence. labels Jul 11, 2026
Codex review noted eventMatchesAnyConfiguredShortcut checked prompt
assignments and command mode but skipped the prompt-mode shortcut
(promptModeShortcut / handlePromptModeKeyDown). A prompt-mode binding on
Cmd+Tab/Space would still be swallowed. Add it so the guard covers every
configured shortcut the keyDown path matches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Alshekhi

Copy link
Copy Markdown
Author

Thanks for the automated reviews — I've addressed every point:

  • Fast-path could swallow configured shortcuts (Greptile P1 / Codex P2): handleKeyEvent's fast-path now checks eventMatchesAnyConfiguredShortcut(...) before passing an event through, covering cancel, paste-last, prompt assignments, prompt-mode, command, rewrite, and primary shortcuts. A Fluid binding on Cmd+Tab/Space is no longer silently discarded.
  • Modifier-only bookkeeping skipped (Codex P2): markOtherInputDuringModifierOnly() is now called before the pass-through, so a system chord pressed during a modifier-only shortcut isn't mistaken for a clean tap on release.
  • Keycode 50 is ANSI-only (Greptile P2): dropped it. The fast-path now handles only Tab(48) and Space(49), which are layout-stable hardware keycodes; Cmd+` falls through to normal handling.
  • Also extracted the CGEventFlagsNSEvent.ModifierFlags mapping into modifierFlags(from:) so the tap and the matcher share one source of truth.

Scope: this PR addresses root causes 1 and 3 of #401 (bottom-overlay accessibility + event-tap latency). Root cause 2 (DynamicNotchKit's DynamicNotchPanel overriding canBecomeKey/canBecomeMain) lives in the DynamicNotchKit dependency and only affects the non-default notch overlay, so it's out of scope here and tracked in #401.

Verified locally on Apple Silicon, macOS 26.5.2, with VoiceOver enabled: Cmd+Tab/Cmd+Space are no longer sluggish and the recording overlays are no longer announced by or focusable in VoiceOver.

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.

1 participant