Fix cursor langbar - #1895
Open
VimWei wants to merge 18 commits into
Open
Conversation
- Read TF_CONVERSIONMODE_NATIVE flag from the compartment on GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION change - Compare with current _status.ascii_mode and toggle via _HandleLangBarMenuSelect when they differ - Use the same IPC path (TrayCommand → SetOption) as the Shift key for consistent behavior - Clear pending composition on mode switch to avoid stale preedit Closes rime#1371
- Replace reading compartment value and comparing with _status.ascii_mode by a blind toggle, matching the OPENCLOSE handler's Shift-key path. The OnChange notification may deliver stale values, making the read-compare approach unreliable. - Remove _IsKeyboardOpen() guard; call _SetKeyboardOpen(true) to ensure the keyboard is active when processing external changes. - Add _updatingLanguageBar re-entrancy guard in _UpdateLanguageBar to prevent _SetCompartmentDWORD from re-triggering the handler. - Add _SetKeyboardOpen/_EnableLanguageBar calls to match the OPENCLOSE handler's else branch.
- Realign _HandleLangBarMenuSelect ternary to one-line condition with colon continuation aligned under the question mark, per Chromium clang-format style; resolves CI lint violations at lines 275-276
…ndows 11 compatibility blind toggle (_status.ascii_mode = !_status.ascii_mode) relies on the _updatingLanguageBar boolean guard catching synchronous OnChange re-entrancy from _UpdateLanguageBar. On Windows 11, TSF may deliver OnChange asynchronously or inject extra compartment writes via TextInputHost.exe, causing the guard to miss and producing an odd number of toggles (wrong mode). Replace with reading the actual compartment value and comparing against _status.ascii_mode. This is naturally idempotent: re-entrant or delayed OnChange reads the value written by _UpdateLanguageBar, finds it matches current state, and skips. The _updatingLanguageBar guard remains as a first layer. Also update analysis doc with Windows 11 root cause and fix rationale.
Restore conversion-compartment-fix-analysis.md to its original state (documenting the blind-toggle approach for Windows 10). Create new windows11-compatibility-fix.md for the value-driven fix that addresses Windows 11 TSF async callback behavior.
Update windows11-compatibility-fix.md with the finding that Windows 11 TSF only triggers OnChange for writes from activated clients. im-control was using TF_CLIENTID_NULL, so OnChange was never fired.
…teLanguageBar Log compartment value, desiredAsciiMode, _status.ascii_mode, _updatingLanguageBar state, and decision (skip/process) in _HandleCompartment. Log flags and _updatingLanguageBar state in _UpdateLanguageBar. Use DebugView to diagnose Windows 11 behavior.
…ingW Add _DbgInit/_DbgLog helpers that append to C:\Users\Public\weasel-compartment-debug.log (always-on for diagnosis). Thread-safe with CRITICAL_SECTION, with timestamps. Log OPENCLOSE/CONVERSION OnChange entry, decision branches, and _UpdateLanguageBar guard transitions.
The CONVERSION handler called _SetKeyboardOpen(true) which writes OPENCLOSE=1 unconditionally. On Windows 11 this triggers the OPENCLOSE OnChange sink (else branch for _isToOpenClose=false), which blind- toggles ascii_mode, reversing the mode that CONVERSION just set. Debug logs confirmed the cascade: 1. CONVERSION sets ascii 0->1 (correct) 2. _SetKeyboardOpen(true) writes OPENCLOSE 3. OPENCLOSE handler toggles ascii 1->0 (wrong) 4. Final: ascii=0 (Chinese instead of English) Per Weasel maintainer guidance: OPENCLOSE and CONVERSION are separate compartments with separate responsibilities. OPENCLOSE manages enable/disable, CONVERSION manages Chinese/English. The CONVERSION handler should not touch OPENCLOSE. Callers (e.g. im-control/vim plugin) always send both -k open and -c together, so OPENCLOSE is handled by its own handler. When only CONVERSION is written (keyboard already open), OPENCLOSE handler doesn't fire and there is no cascade.
Record the ThreadMgr pointer and thread ID when WeaselTSF is activated, to compare with im-control's TF_GetThreadMgr pointer and determine if they share the same ThreadMgr instance in Windows Terminal.
The C:\Users\Public path may not be writable from all processes (e.g. UWP-packaged apps like Windows Terminal). Use GetTempPathA to write to the per-user temp directory. Also log PID in session header to distinguish multiple processes.
Remove _DbgInit/_DbgLog infrastructure, all debug log calls in Compartment.cpp, LanguageBar.cpp, and WeaselTSF.cpp. The production code is clean and contains only the functional fixes: - CONVERSION handler is value-driven (reads compartment, compares with state, only acts if different) - CONVERSION handler no longer calls _SetKeyboardOpen(true)
Add three-layer root cause analysis (TF_GetThreadMgr singleton, TfClientId, OPENCLOSE cascade), document the compartment decoupling principle, and add deployment note about restarting applications after DLL replacement.
Merge conversion-compartment-fix-analysis.md (blind toggle, Win10) and windows11-compatibility-fix.md (value-driven, Win11) into one coherent document: compartment-external-control-fix.md. The new document covers all four root causes, the complete fix across Weasel/im-control/vim-plugin, design principles, DLL deployment instructions (preserved from original), and verification results for both Windows 10 and 11.
- Call _SetKeyboardOpen(true) in CONVERSION handler only when _isToOpenClose is true (Win10, ToggleImeOnOpenClose=yes), since the OPENCLOSE if-branch does not auto-reopen the keyboard - Skip _SetKeyboardOpen when _isToOpenClose is false (Win11) to avoid triggering the OPENCLOSE else-branch blind toggle that cascades and reverts the ascii_mode just set by CONVERSION - Add value-matches branch that also reopens keyboard on Win10 when CONVERSION value already matches status but keyboard is closed - Remove all debug logging (_DbgLog function, s_dbg* state, calls) - Update compartment-external-control-fix.md with the fifth root cause, the registry-based conditional reopen, and Win10/Win11 difference table
When external tools (AppIME, im-control, vim-im-select) switch RIME ascii_mode via TSF compartment writes, the cursor LangBar button (_pLangBarButton) stayed stale while tray icon and taskbar indicator updated correctly. Root causes and fixes in WeaselTSF compartment handling: 1. OPENCLOSE handler if-branch (Win10, _isToOpenClose=true): sync _status.ascii_mode from CONVERSION compartment before calling _UpdateLanguageBar, preventing stale _status from overwriting the compartment value that an external writer just set. 2. OPENCLOSE handler else-branch (Win11, _isToOpenClose=false): replace the historical blind toggle (_status.ascii_mode = ! _status.ascii_mode) + _HandleLangBarMenuSelect + _UpdateLanguageBar with a value-driven approach that reads CONVERSION compartment and syncs _status without writing back (avoiding overwrite of external writes). Add a directional force: when OPENCLOSE was just closed (e.g. gvim Esc) and _status.ascii_mode is still Chinese, force _status to English, notify RIME via _HandleLangBarMenuSelect (ENABLE_ASCII), and refresh LBB. This covers the gvim insert- Chinese -> 2s+ idle -> Esc case where RIME server-side ascii_composer may have already auto-toggled but client _status is stale. The force is idempotent: im-control -c alphanumeric arriving later finds compartment already ~NATIVE and skips SetValue. 3. CONVERSION handler: replace _UpdateLanguageBar call (which writes compartment inside OnChange callback and triggers TSF E_UNEXPECTED) with _pLangBarButton->UpdateWeaselStatus(_status) for direct LBB refresh. Schedule CUpdateLangBarEditSession for async compartment write outside the callback. 4. Add _ReconcileCompartment() (LanguageBar.cpp): pure local compare of _status vs CONVERSION compartment, refreshes LBB on mismatch. Zero IPC, zero broadcast. Called by a 2s SetTimer on a message-only window (_InitDeferredWindow/_DeferredWndProc) as a safety net for missed OnChange events (Win11 gvim race). Also called from OnSetThreadFocus before pulling server authoritative status. 5. _UpdateLanguageBar: add _pLangBarButton null check. 6. OnSetThreadFocus: call _ReconcileCompartment before the server handshake (ProcessKeyEvent(0) + GetResponseData) so stale local state is corrected before _UpdateLanguageBar writes compartment. Files changed: WeaselTSF/Compartment.cpp OPENCLOSE/CONVERSION handler rewrite WeaselTSF/LanguageBar.cpp _ReconcileCompartment + null guard WeaselTSF/WeaselTSF.cpp deferred window + 2s timer + focus reorder WeaselTSF/WeaselTSF.h new method/member declarations WeaselTSF/stdafx.h add <strsafe.h> Paired with: im-control: hook.cpp OPENCLOSE reopen condition (separate repo) VimReader: IME_SetEnglishViaF13 im-control + LCtrl path (separate repo) See docs/cursor-langbar-fix.md for the scenario verification table and design principles. Debug process logs are in docs/archives/.
New docs/cursor-langbar-fix.md: concise PR-level summary with root causes, fixes, scenario verification table, design principles, and maintenance pitfall warnings. Archived: docs/archives/cursor-indicator-debug.md full v1-v10 debug process docs/archives/status-icon-debug.md early status icon exploration docs/archives/compartment-external-control-fix.md historical fix
There was a problem hiding this comment.
Pull request overview
This PR refines WeaselTSF’s handling of TSF compartment changes to ensure the cursor-adjacent Language Bar Button (LBB, _pLangBarButton) reliably refreshes when external tools (e.g., im-control/AppIME/vim-im-select) switch ASCII/native mode—especially across Win10/Win11 differences and TSF callback constraints.
Changes:
- Adds deferred/periodic reconciliation via a message-only window to mitigate missed or unsafe compartment updates.
- Refactors OPENCLOSE and CONVERSION compartment handlers to avoid blind toggles and avoid writing compartments directly inside
OnChangepaths. - Adds documentation describing the root cause and the fix strategy.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| WeaselTSF/WeaselTSF.h | Declares new reconciliation/deferred-window helpers and state flags. |
| WeaselTSF/WeaselTSF.cpp | Creates/destroys message-only window, adds timer + deferred update path, adjusts focus refresh flow. |
| WeaselTSF/stdafx.h | Adds <strsafe.h> include. |
| WeaselTSF/LanguageBar.cpp | Adds _ReconcileCompartment() and updates _UpdateLanguageBar() behavior/guards. |
| WeaselTSF/Compartment.cpp | Updates OPENCLOSE/CONVERSION compartment handling; introduces edit-session-based deferred updates. |
| docs/cursor-langbar-fix.md | New user-facing/maintainer documentation for the cursor LBB refresh issue and fix. |
| docs/compartment-external-control-fix.md | Adds extended analysis and rationale around external compartment control behavior. |
| docs/archives/status-icon-debug.md | Adds historical debugging record. |
| docs/archives/cursor-indicator-debug.md | Adds comprehensive archived debugging timeline and experiments. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+327
to
+336
| com_ptr<CUpdateLangBarEditSession> pSession; | ||
| pSession.Attach(new CUpdateLangBarEditSession(this, pContext)); | ||
| if (pSession) { | ||
| HRESULT hr; | ||
| pContext->RequestEditSession( | ||
| _tfClientId, pSession, | ||
| TF_ES_ASYNCDONTCARE | TF_ES_READWRITE, &hr); | ||
| pSession.Release(); | ||
| editSessionScheduled = true; | ||
| } |
Comment on lines
+267
to
+280
| DWORD convFlags; | ||
| if (SUCCEEDED(_GetCompartmentDWORD(convFlags, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION))) | ||
| _status.ascii_mode = !(convFlags & TF_CONVERSIONMODE_NATIVE); | ||
| _UpdateLanguageBar(_status); | ||
| } else { | ||
| _status.ascii_mode = !_status.ascii_mode; | ||
| DWORD ocFlags = 0; | ||
| _GetCompartmentDWORD(ocFlags, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE); | ||
| BOOL keyboardJustClosed = (ocFlags == 0); | ||
| DWORD convFlags = 0; | ||
| bool desiredAscii = false; | ||
| bool haveConv = SUCCEEDED(_GetCompartmentDWORD(convFlags, | ||
| GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION)); | ||
| if (haveConv) | ||
| desiredAscii = !(convFlags & TF_CONVERSIONMODE_NATIVE); |
Comment on lines
+402
to
+415
| void WeaselTSF::_ReconcileCompartment() { | ||
| DWORD flags; | ||
| _GetCompartmentDWORD(flags, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION); | ||
| bool compartmentAscii = !(flags & TF_CONVERSIONMODE_NATIVE); | ||
| if (compartmentAscii != _status.ascii_mode) { | ||
| _status.ascii_mode = compartmentAscii; | ||
| _HandleLangBarMenuSelect(compartmentAscii | ||
| ? ID_WEASELTRAY_ENABLE_ASCII | ||
| : ID_WEASELTRAY_DISABLE_ASCII); | ||
| if (_pLangBarButton) { | ||
| _pLangBarButton->UpdateWeaselStatus(_status); | ||
| } | ||
| } | ||
| } |
Comment on lines
417
to
423
| void WeaselTSF::_UpdateLanguageBar(weasel::Status stat) { | ||
| if (!_pLangBarButton) | ||
| return; | ||
| DWORD flags; | ||
| _GetCompartmentDWORD(flags, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION); | ||
| if (stat.ascii_mode) | ||
| flags &= (~TF_CONVERSIONMODE_NATIVE); | ||
| else | ||
| flags |= TF_CONVERSIONMODE_NATIVE; |
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.
基于 #1889 的进一步修订,增加了两个 commit
光标语言栏按钮不刷新修复
问题
使用 AppIME / im-control / vim-im-select 外部工具切换 RIME/weasel 输入法中英文状态时,光标位置的语言栏按钮(
_pLangBarButton)图标不更新——右下角托盘图标和任务栏指示器始终正确,唯独光标附近的语言栏按钮停留在旧状态。三个层级
_pLangBarButton->UpdateWeaselStatus()根因
1. OPENCLOSE handler else 分支盲 toggle +
_UpdateLanguageBar写回 compartment(race)Win11(
_isToOpenClose=false)时 OPENCLOSE else 分支盲目翻转_status.ascii_mode,然后调_UpdateLanguageBar写 CONVERSION compartment。当外部工具(im-control)同时写 compartment 时,两条 OnChange 的到达顺序不确定——若 OPENCLOSE 后到,会覆盖外部写入,产生死锁。2. CONVERSION handler 在 OnChange 回调中写 compartment 失败
_HandleCompartment(CONVERSION)在OnChange回调内调_UpdateLanguageBar写 compartment 会触发 TSFE_UNEXPECTED,导致 LBB 不刷新。3.
_status陈旧导致方向性 force 缺失用户 gvim insert 中文模式停顿 2s+ 后 Esc,RIME server 端
ascii_composer可能已自动切回 ascii=true,但客户端_status未通过DoEditSession更新仍为 false(中文)。此时 im-control 读 compartment 发现值未变跳过 SetValue,无 OnChange 触发,LBB 停留在"中"。修复
WeaselTSF 端(本 PR)
WeaselTSF/Compartment.cpp_isToOpenClose=true):调_UpdateLanguageBar前先从 CONVERSION compartment 同步_status,避免陈旧状态覆盖 compartment_isToOpenClose=false):_status.ascii_mode = !_status.ascii_mode_HandleLangBarMenuSelect+_UpdateLanguageBar(避免覆盖外部写入)_status,仅在 mismatch 时刷 LBBkeyboardJustClosed)且_status.ascii_mode仍是中文时,主动切英文(通知 RIME + 写 compartment + 刷 LBB)。覆盖 gvim Esc 场景_pLangBarButton->UpdateWeaselStatus(_status)替代_UpdateLanguageBar(避免在 OnChange 回调中写 compartment),并调度CUpdateLangBarEditSession异步刷新WeaselTSF/LanguageBar.cpp_ReconcileCompartment():纯本地比对_status与 compartment,mismatch 时同步并刷 LBB。零 IPC、零广播_UpdateLanguageBar加_pLangBarButton空指针保护WeaselTSF/WeaselTSF.cpp_InitDeferredWindow/_UninitDeferredWindow/_DeferredWndProc:message-only 隐藏窗口 + 2sSetTimer周期调_ReconcileCompartment,作为 OnChange 漏检的安全网OnSetThreadFocus:先_ReconcileCompartment再从 server 拉权威态 +_UpdateLanguageBarWeaselTSF/WeaselTSF.h— 新方法/成员声明WeaselTSF/stdafx.h— 加<strsafe.h>(StringCchPrintfW)im-control 端(配套 PR)
injector/hook.cpp:OPENCLOSE 重开条件g_isToOpenClose && conversionModeNative.has_value() && !keyboardOpenClose——不再解引用*conversionModeNative(只在-c native时重开会漏掉-c alphanumeric场景导致 Win10 gvim RIME 被禁用)。VimReader 端(配套修改)
IME.ahk新增IME_SetEnglishViaF13():IME_SetAlphanumeric()(im-control 值驱动切状态)+Send {LControl}(触发前台 WeaselTSFOnKeyDown→DoEditSession拉 server 权威态刷 LBB)。LCtrl 不生成 WM_CHAR(不在任何 app 中插入可见字符),不被 RIMEascii_composer处理(无 blind toggle 副作用)。AppIME.ahk:两处触发点改用IME_SetEnglishViaF13()。场景验证
!_status.ascii_mode)设计原则
_status决定方向,不盲目翻转_UpdateLanguageBar写 compartment_isToOpenClose分支隔离:Win10(if)和 Win11(else)行为独立,互不干扰维护踩坑警示
_status_status;仅keyboardJustClosed && !_status.ascii_mode时方向性 force_UpdateLanguageBar写 compartmentUpdateWeaselStatus替代;或PostMessage延迟到消息泵*conversionModeNative解引用has_value(),alphanumeric 也需要重开 OPENCLOSE<F13>字面文本,改用 LCtrl_ReconcileCompartment定时器相关文档
docs/compartment-external-control-fix.md— 历史 compartment 外部控制修复docs/archives/cursor-indicator-debug.md— 完整调试过程记录(v1-v10 演进)docs/archives/status-icon-debug.md— 早期状态图标探索(术语混淆,保留原始状态)