Skip to content

После переключения раскладки клавишей Globe (🌐) нажатия теряются до клика мышью #15

Open

Description

@8ui

Кратко

После переключения раскладки клавишей Globe (🌐) все следующие нажатия выпадают из буфера слова, пока не кликнешь мышью. В режиме хоткея хоткей срабатывает с пустым буфером, и ничего не исправляется. В зависимости от тайминга иногда теряется только первая буква: ghbdtn → gпривет, fylhtq → fндрей.

В Electron-приложениях (например, десктопный Claude) воспроизводится каждый раз, в нативных (Sublime Text) — через раз.

Окружение

  • SwitchFix v0.0.9 (d31f7b6)
  • macOS 27.0, Apple Silicon; Swift 6.4
  • Режим коррекции: хоткей. Раскладки: ABC + Russian – PC, переключение клавишей Globe

Причина

  1. keyDown клавиши Globe (keyCode 179) классифицируется как .navigation, а она invalidatesFocus. Контекст получает secureFocus = .unknown с эпохой E, и через 25 мс запланирован запрос фокуса для E (focusMayChange).
  2. Примерно через 15 мс срабатывает selectedInputSourceChanged. Для не-сгенерированной смены он вызывает replaceContext(..., secureFocus: current.secureFocus): эпоха становится E+1, а статус остаётся .unknown.
  3. Отложенный запрос возвращается для эпохи E, и CaptureStateStore.resolveFocus отбрасывает его из-за несовпадения эпохи. Для E+1 повторного запроса никто не делает, поэтому canBuffer остаётся false и каждый символ инвалидируется.

Debug-лог: нажатие Globe, затем набор, и focus resolved нет до клика:

input seq=402 kind=<private> keyCode=179 ...
buffer invalidated reason=<private>
[SwitchFix] layout changed old=russian new=english generated=false
input seq=403 kind=<private> keyCode=5 ...
buffer invalidated reason=<private>
input seq=404 kind=<private> keyCode=4 ...
buffer invalidated reason=<private>
...
manual: bufferLen=0 secureFocus=unknown appAllowed=true

(Последняя строка — из локального диагностического лога, добавленного в requestManualCorrection.)

Исправление

После ручной смены раскладки перезапрашивать фокус для новой эпохи, так же как это уже делает ветка сгенерированного переключения:

         } else {
             inputEngine?.handleLayoutChange(
                 from: oldLayout,
                 to: newLayout,
                 context: context,
                 fromVariant: fromVariant,
                 toVariant: toVariant
             )
+            // The switch key (e.g. Globe) is captured as navigation and leaves focus
+            // unknown; the new epoch orphans that pending query, so re-resolve here —
+            // otherwise every keystroke is dropped until the next click.
+            if context.secureFocus != .notSecure {
+                focusCoordinator?.focusMayChange(pid: context.frontmostPID, epoch: context.epoch)
+            }
         }

Проверено локально: после правки Globe → ghbdtn → хоткей даёт привет и в десктопном Claude, и в Sublime Text.

Activity

  1. Hantok commented on Oct 20, 2026

    @Hantok
    Member

    Pre-merge audit against released v0.0.11 and PR #22 (commit 68873cc): still unresolved. No verified fixed version yet.

    In AppDelegate.selectedInputSourceChanged(), a manual layout change creates a new context epoch while preserving the previous secure-focus state. Only the generated-selection branch schedules a fresh focus query. The reported stale-epoch/unknown-focus path therefore remains possible.

    Proposed fix:

    1. After handling a manual layout change, request focus resolution for the new PID/epoch whenever the inherited focus state is not .notSecure.
    2. Add a regression check for navigation → unknown focus at epoch E → manual source change to E+1 → stale E result discarded → fresh E+1 result restores buffering. Retain fail-closed behavior for secure focus.
    3. Verify Globe → type → correction in both a native editor and an Electron app, including rapid repeated switches.

    Leaving open until this path is fixed and verified.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions