fix(ui): follow-up fixes for the ascii_mode status icon - #1159
Conversation
| // 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
補充一個實測數據:在 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 的提前返回吞掉。
| let label = "ascii_mode".withCString { name in | ||
| rimeAPI.get_state_label_abbreviated(session, name, asciiMode, true).asString | ||
| } | ||
| NSApp.squirrelAppDelegate.updateStatusIcon(asciiMode: asciiMode, schemaLabel: label) |
There was a problem hiding this comment.
Calling updateStatusIcon twice in a row - unnecessary?
I guess the next status icon update at L208 is what user end up seeing.
There was a problem hiding this comment.
確實多餘——是把分支 rebase 到新 master 時沒注意到 activateServer 末尾已經有同樣的邏輯,重複帶入了一份。已刪除,只保留 master 原有的那一處。
…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>
e82e393 to
43e14b5
Compare
| if keyboardLayout != "" { | ||
| client?.overrideKeyboard(withKeyboardNamed: keyboardLayout) | ||
| } | ||
| // Activation delivers no flagsChanged event, and NSEvent.modifierFlags |
There was a problem hiding this comment.
Question: what if other modifiers changed in another process?
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
flagsChangedevent, so state established while another input source was active goes unnoticed:lastModifiersmay 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 inhandle(). Seed the bit fromCGEventSource.flagsState(.combinedSessionState)—NSEvent.modifierFlagsonly reflects this process's own event stream and is stale at activation time.switches/@N/resetwithout 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_optionis deliberately not used for this: re-asserting an option fires the notification and would flash the status bubble on every app switch. Nor isascii_modeever 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:)onDistributedNotificationCenterregisters 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 withsuspensionBehavior: .deliverImmediatelyinstead; the handler also keeps performing the #1140 stranded-composition cleanup.Testing
show_notifications_when: appropriate, no status-bubble flashes on app switches.🤖 Generated with Claude Code