Skip to content

Keyboard sounds can be suppressed by later CGEventTap consumers #4

Description

@longyijdos

Problem

KeyFoley currently monitors ordinary keyboard input with a session-level CGEventTap using .headInsertEventTap and .listenOnly.

This makes playback depend on the ordering of event taps installed by other applications. A reproducible example is HyperSwitch, which registers global shortcuts such as Cmd+Tab and `Cmd+``.

Reproduction

  1. Start KeyFoley.
  2. Start HyperSwitch afterwards.
  3. Hold Cmd and press Tab or `.
  4. HyperSwitch handles the shortcut, but KeyFoley does not play the corresponding key sound.
  5. Restart KeyFoley while HyperSwitch remains running.
  6. The same shortcuts now produce sound normally.

Expected behavior

KeyFoley should play sounds for physical keyboard presses regardless of whether another application later consumes the corresponding macOS keyboard event.

Current behavior

The behavior depends on startup / event-tap registration order. A later application can insert its tap ahead of KeyFoley and consume an event before KeyFoley receives it.

Why this happens

The ordinary keyboard path currently uses:

CGEvent.tapCreate(
    tap: .cgSessionEventTap,
    place: .headInsertEventTap,
    options: .listenOnly,
    ...
)

.listenOnly prevents KeyFoley itself from modifying the event stream, but does not guarantee that KeyFoley receives events discarded by an earlier active event tap.

As a result, applications that intercept global shortcuts can suppress KeyFoley playback depending on tap ordering.

Workaround

Restart KeyFoley after the conflicting application has started. This recreates KeyFoley's head-insert tap and places it ahead of existing taps again.

This is only a temporary workaround: if the other application restarts or recreates its tap later, the issue can return.

Possible direction

Evaluate making IOHIDManager the primary source for ordinary physical keyboard events, while keeping the existing MonitoredKeyEvent boundary so the upper layers (AppModel, sound playback, pet handling, diagnostics) do not need a broad rewrite.

KeyFoley already uses IOHID for Caps Lock, so that implementation can serve as a starting point for a generalized keyboard HID monitor.

A possible target architecture:

IOHIDManager
  └─ ordinary keyboard / modifier events

CGEventTap
  └─ system-defined / auxiliary events or fallback cases

        ↓
MonitoredKeyEvent
        ↓
existing playback / pet / diagnostics pipeline

Important migration work would include:

  • HID usage → existing virtual-key-code / internal event mapping
  • modifier press/release semantics
  • auto-repeat behavior
  • multiple keyboards and device hot-plugging
  • avoiding duplicate events during migration
  • retaining CGEventTap only where macOS-level event semantics are actually needed

Input Monitoring permission would still be required; this change is intended to make physical key detection independent of downstream CGEventTap consumers and startup order, not to bypass macOS privacy controls.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions