[BUG] Modifier-only stop press is dropped when the health check rebuilds the event tap

Closed
#1,148 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
40/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
swift
Domain
desktop

Research direction

Start in GlobalHotkeyManager.swift at cleanupEventTap, which calls only clearPrimaryShortcutPressState, and compare it with handleTapDisableEvent, which calls resetModifierOnlyShortcutTracking. Read evaluate and flagsChanged to see how modifierOnlyKeyDown, activeModifierOnlyType and pressedModifierKeyCodes block the next press. Done when a tap rebuild mid-press leaves no stale modifier-only state and the next full press of the shortcut works. The issue proposes a quarantine rule that needs maintainer agreement before you implement it.

Written by the indexing model from the issue text.

Description

stale
Describe the bug

When the 30s health check finds the keyboard tap disabled (GlobalHotkeyManager.swift:2513-2515), it calls setupGlobalHotkey() → cleanupEventTap() (:737-738, :799-805). cleanupEventTap only calls clearPrimaryShortcutPressState() (:1168-1185). That clears isKeyPressed and the .transcription automatic/hold tracking, but leaves the modifier-only state in place: modifierOnlyKeyDown, activeModifierOnlyType, activeModifierOnlyShortcut, pressedModifierKeyCodes, otherKeyPressedDuringModifier.

handleTapDisableEvent (:2728) does a full resetModifierOnlyShortcutTracking(reason: .tapDisabled) (:2740), but only when a tapDisabledBy* event is delivered. In the cases we observed, no such event arrived and the tap was rebuilt only by the health check.

Effect when a modifier-only stop press is in flight at the rebuild:

  • If the key-up was missed while the tap was disabled, activeModifierOnlyType stays set and modifierOnlyKeyDown stays true. Any typing or click in the meantime marks the stale press as interrupted (:1188-1191). On the next press, evaluate will not start a new press because activeModifierOnlyType != nil (:125), and it ignores the key-down because the key is already in pressedModifierKeyCodes (:149). The key-up then ends the stale press as not clean, and the stop is rejected with "Transcription modifier released but another key was pressed - ignoring".
  • If the key-up does reach the new tap, hold mode still does not stop: isKeyPressed was cleared (:1177), so finishModifierOnlyPress skips the .hold branch (:1926-1927). Automatic mode loses the start time and wasTargetActive (:500-501).

synchronizedPressedModifierKeyCodes does not recover this. It only trims key codes whose modifier flag is no longer active; it never clears activeModifierOnlyType or modifierOnlyKeyDown.

Actual behavior

The stop press is logged with targetActive=true. The health check then finds the tap disabled and rebuilds it mid-press, and the release produces no stop. Escape was the only way out.

Expected behavior

A tap rebuild discards any half-finished press as a whole, and the next complete press of the shortcut works normally.

Fix
  1. cleanupEventTap resets all in-flight press state when the tap is replaced: the modifier-only fields above, all mode-pressed flags, and the automatic/hold tracking for every mode. It does not stop or cancel the recording.
  2. If a modifier press was in flight, flagsChanged events are ignored until an event arrives with no relevant modifiers down. The orphaned release or a partial press then cannot start or stop anything, and the next complete press is evaluated from a clean state.

This makes the tap-rebuild path consistent with the tap-disabled path, without ending the recording. Cost: if the key-up was missed while the tap was down, the first full press after recovery only clears the quarantine, so one extra press is needed to stop.

App Version

1.6.12-beta.1 (2ca292d1)

Dominant language
Swift
Stars
11.9k
Forks
863
Avg merge
1d 20h
Merged PRs (30d)
36

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from altic-dev/FluidVoice

All issues in altic-dev/FluidVoice

Similar issues

More Swift issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.