Skip to content

fix(ui): follow-up fixes for the ascii_mode status icon - #1159

Merged
lotem merged 2 commits into
rime:masterfrom
metalbreeze:fix/status-icon-followups
Aug 3, 2026
Merged

fix(ui): follow-up fixes for the ascii_mode status icon#1159
lotem merged 2 commits into
rime:masterfrom
metalbreeze:fix/status-icon-followups

Conversation

@metalbreeze

Copy link
Copy Markdown
Contributor

Summary

Two follow-up fixes for the menu-bar status icon introduced in #1122, found while testing across input-source switches.

1. Keep status icon and modifier state in sync on activation

Switching to Squirrel produces no flagsChanged event, so state established while another input source was active goes unnoticed:

  • lastModifiers may hold a stale Caps Lock bit; the user's first Caps Lock press after switching in then compares equal to it and is swallowed by the early-return in handle(). Seed the bit from CGEventSource.flagsState(.combinedSessionState)NSEvent.modifierFlags only reflects this process's own event stream and is stale at activation time.
  • Sessions are torn down and rebuilt around input-source switches, and a fresh session initializes its options from the schema's switches/@N/reset without firing any option notification — the icon keeps showing the pre-switch state while the session actually starts in the reset state (the icon says 中 while typing produces English). Repaint the icon silently from the actual state on every activation. set_option is deliberately not used for this: re-asserting an option fires the notification and would flash the status bubble on every app switch. Nor is ascii_mode ever written from an observed Caps Lock — macOS clears Caps Lock whenever the input source changes, so a Caps Lock seen at activation is transient and acting on it would race that clearing.

2. Hide the status icon reliably when switching away from Squirrel

The block-based addObserver(forName:object:queue:using:) on DistributedNotificationCenter registers with a suspension behavior that holds notifications back while the process is not active — which is precisely Squirrel's state right after the user switches to another input source. The "hide now" notification sat undelivered until the next activation, so the icon lingered in the menu bar while ABC was selected. Register selector-based with suspensionBehavior: .deliverImmediately instead; the handler also keeps performing the #1140 stranded-composition cleanup.

Testing

  • Toggling Shift / Caps Lock within Squirrel updates the icon as before.
  • Switching Squirrel → ABC hides the icon immediately; ABC → Squirrel shows it with the session's true mode and schema label.
  • Fresh sessions (relaunch, input-source round-trips) show the schema's reset state truthfully — no more 中-icon-but-English-typing.
  • With show_notifications_when: appropriate, no status-bubble flashes on app switches.

🤖 Generated with Claude Code

@lotem
lotem requested a review from LEOYoon-Tsaw July 27, 2026 03:06
Comment thread sources/SquirrelInputController.swift Outdated
// is stale here; query the session-wide hardware state instead.
let capsLockOn = CGEventSource.flagsState(.combinedSessionState).contains(.maskAlphaShift)
// Do NOT force ascii_mode from a pre-existing Caps Lock here: macOS clears
// Caps Lock whenever the input source changes, so a Caps Lock observed at

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

macOS clears Caps Lock whenever the input source changes

This is the behaviour when the system setting "Use Caps Lock to switch to and from ABC" is checked.
When unchecked, Caps Lock stays on after switching input source.

I'm confused: if the PR assumes that "macOS clears Caps Lock after input source changes", why is the following code block necessary?

only repaint the icon from the session's actual state

Isn't that the same state being displayed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

補充一個實測數據:在 macOS 26.5.2(25F84)、「使用大寫鎖定鍵切換 ABC」未勾選defaults read -g TISRomanSwitchState = 0)的環境下,Caps Lock 亮起時在兩個非鼠鬚管輸入法之間切換,Caps 也會被系統清除——所以這個行爲似乎不只取決於那個勾選項,可能與 macOS 版本有關。

不過這點已不影響本 PR:那段註釋連同下面的重繪塊都已刪除(重繪與 activateServer 末尾已有的那處重複,見另一條回覆)。這個 commit 現在只保留 lastModifiers 播種——它直接讀取激活當下的硬件 Caps 狀態,系統清不清 Caps 都同樣成立:激活時沒有 flagsChanged 事件,而 NSEvent.modifierFlags 只反映本進程自己的事件流,若 lastModifiers 與實際狀態脫節,用戶切回後第一次按 Caps 會被 lastModifiers == modifiers 的提前返回吞掉。

Comment thread sources/SquirrelInputController.swift Outdated
let label = "ascii_mode".withCString { name in
rimeAPI.get_state_label_abbreviated(session, name, asciiMode, true).asString
}
NSApp.squirrelAppDelegate.updateStatusIcon(asciiMode: asciiMode, schemaLabel: label)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Calling updateStatusIcon twice in a row - unnecessary?

I guess the next status icon update at L208 is what user end up seeing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

確實多餘——是把分支 rebase 到新 master 時沒注意到 activateServer 末尾已經有同樣的邏輯,重複帶入了一份。已刪除,只保留 master 原有的那一處。

shu.nie and others added 2 commits July 31, 2026 01:16
…ation

Activation delivers no flagsChanged event, and NSEvent.modifierFlags
only reflects this process's own event stream, so lastModifiers may
disagree with the actual Caps Lock state when the user switches back to
Squirrel (e.g. after toggling Caps Lock in another input source). The
next Caps Lock press then compares equal to the stale lastModifiers and
is dropped by the early-return in handle(), desyncing ascii_mode from
the keyboard.

Seed the capsLock bit from
CGEventSource.flagsState(.combinedSessionState) so the next
flagsChanged event computes its delta against reality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The status icon's visibility follows the
kTISNotifySelectedKeyboardInputSourceChanged distributed notification.
The block-based addObserver(forName:object:queue:using:) registers with
a suspension behavior that holds notifications back while the process
is not active — which is precisely Squirrel's state right after the
user switches to another input source. The "hide now" notification then
sat undelivered until the next activation, so the icon lingered in the
menu bar while another input source was selected.

Register with suspensionBehavior: .deliverImmediately (selector-based
API) so the notification arrives regardless of activation state, and
hop to the main queue before touching NSStatusItem. The handler keeps
performing the stranded-composition cleanup (rime#1140). The observer is
already removed in applicationWillTerminate via
DistributedNotificationCenter.removeObserver(self).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@metalbreeze
metalbreeze force-pushed the fix/status-icon-followups branch from e82e393 to 43e14b5 Compare July 30, 2026 17:17
if keyboardLayout != "" {
client?.overrideKeyboard(withKeyboardNamed: keyboardLayout)
}
// Activation delivers no flagsChanged event, and NSEvent.modifierFlags

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Question: what if other modifiers changed in another process?

@lotem
lotem merged commit 8418c95 into rime:master Aug 3, 2026
1 check passed
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.

2 participants