fix(tao/macos): keep the input-source indicator off a caret that is gone - #690
Merged
Merged
Conversation
The badge a Caps Lock bound to keyboard-layout switching raises is a window HIToolbox creates *inside our own process*, positioned from whatever `firstRectForCharacterRange:` answers. TaoView answered a process-global caret rect that nothing ever invalidated, so once a focused text field was destroyed the badge kept appearing over the spot the field used to occupy. Before any field had been focused it was worse: the overrides were only installed when the first text-input session started, so tao's own implementation answered — the content-rect corner with a *top-down* y handed back as a Cocoa bottom-up coordinate — which parks the badge in the bottom-left corner of the screen of an app that has never shown a text field. Three changes, all measured against a logging `NSTextInputClient` probe driven by `TISSelectInputSource` with the badge window tracked through `CGWindowListCopyWindowInfo`: The cached rect is now scoped to the view that pushed it, and every other answer is `NSZeroRect`. That exact shape is the one AppKit reads as "no insertion point": a zero *size* alone does not suppress the badge — `(0, 30, 0x0)`, which is what tao answers, still draws it — and `selectedRange = NSNotFound` plus `invalidateCharacterCoordinates` change nothing at all. A rect falling outside the key window is not drawn either, which is why the corner case only shows on a window large enough to contain it. The overrides are installed per window creation rather than on the first session, so the answer is ours from the first frame. `TaoView` only exists once a window has been built, and the swizzle is idempotent. The input context is deactivated when the session ends. On its own that is not enough — `interpretKeyEvents:` re-activates it on the next keystroke — but it takes the badge down immediately for an app that is not being typed into. The teardown carries the activation token it was handed, because focus moving between fields starts the incoming session *before* the outgoing one is torn down: without the guard, focusing a second field deactivates the context the first teardown then finds live. Same ordering trap as the document cache.
`MacOsTextInputClientProbe.imeRect` reads what TaoView answers AppKit and reports an all-zero rect as "no insertion point", which is the property both cases assert. `caretRectDiesWithTheFocusedField` walks one field lifecycle: the caret is published, it follows focus to a second field (the ordering guard — without it the run takes three times as long because the incoming session's context is deactivated and has to be re-established), it is dropped when the fields are destroyed, a keystroke does not republish it, and a field composed again gets it back. `noCaretRectBeforeAnyField` covers the window that never shows one. Both fail on the code before this branch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On macOS the badge a Caps Lock bound to keyboard-layout switching raises is a window HIToolbox creates inside our own process, positioned from whatever
firstRectForCharacterRange:answers. TaoView answered a process-global caret rect that nothing ever invalidated, so:Three changes:
NSZeroRect. That exact shape is the only one AppKit reads as "no insertion point": a zero size alone does not suppress the badge ((0, 30, 0x0), what tao answers, still draws it), andselectedRange = NSNotFound+invalidateCharacterCoordinateschange nothing.NSTextInputClientoverrides are installed per window creation instead of on the first text-input session, so the answer is ours from the first frame (TaoViewonly exists once a window is built; the swizzle is idempotent).interpretKeyEvents:re-activates it on the next keystroke — but it takes the badge down immediately for an app that is not being typed into. The token guard matters because focus moving between fields starts the incoming session before the outgoing one is torn down: without it, focusing a second field deactivates the context the first teardown then finds live (same ordering trap as the document cache).Measurements behind each claim came from a logging
NSTextInputClientprobe driven byTISSelectInputSource, with the badge window tracked throughCGWindowListCopyWindowInfo. A rect falling outside the key window is not drawn at all, which is why the corner case only reproduces on a window large enough to contain it.Test plan
macOS caret rect is dropped with the focused fieldandmacOS publishes no caret rect before any text field; both fail on the code before this branch(-25, 1027)before, none after#595Kotoeri live-conversion andNSTextInputClientheadful cases still passTaoPressAndHoldE2ETest— 4/4workspace header drag…, passes on retry) and three that fail identically on a clean tree (#569 popup above the work area,#569 dialog clamped back on,tab hover card)apiCheck,detekt,ktlint,:decorated-window-tao:test