Skip to content

feat: fix TSF compartment external control for Weasel (Win10/Win11), deadlock fixes, --get-keyboard, and user guide#3

Open
VimWei wants to merge 17 commits into
brglng:mainfrom
VimWei:dev
Open

feat: fix TSF compartment external control for Weasel (Win10/Win11), deadlock fixes, --get-keyboard, and user guide#3
VimWei wants to merge 17 commits into
brglng:mainfrom
VimWei:dev

Conversation

@VimWei

@VimWei VimWei commented Jul 9, 2026

Copy link
Copy Markdown

This PR makes im-control's TSF compartment control fully work with Weasel (RIME) on both Windows 10 and Windows 11, and improves robustness across the board.

Core Fix: TSF Compartment Control for Weasel

Previously, programmatically writing GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION via im-control had no effect on Weasel. The root cause differed by OS:

OS Symptom Root Cause Fix
Win10 Compartment write triggers Weasel's OnChange, but Weasel immediately reverts it Weasel's CONVERSION handler queryied RIME backend for current state and wrote it back to the compartment — silently undoing the external change Fixed upstream in Weasel's _HandleCompartment: reads the compartment value instead, compares with current state, only switches if different
Win11 Compartment write never reaches Weasel at all (a) CoCreateInstance(CLSID_TF_ThreadMgr) returns a new instance instead of the per-thread singleton, so the write goes to a different TSF instance; (b) SetValue(TF_CLIENTID_NULL, ...) doesn't trigger OnChange for non-activated clients (a) Use TF_GetThreadMgr export from msctf.dll (033c0cd); (b) Call ITfThreadMgr::Activate for a valid TfClientId (c0b4eae)
Both CONVERSION write could cascade-invert via OPENCLOSE handler Weasel's CONVERSION handler called _SetKeyboardOpen(true), triggering OPENCLOSE handler's blind toggle that flipped the mode back Fixed upstream in Weasel: removed _SetKeyboardOpen(true) from CONVERSION handler, fully decoupling the two compartments

A detailed root cause analysis is available in the Weasel companion repo: compartment-external-control-fix.md.

Additional Robustness Fixes

  • Validate VARIANT type before reading/writing conversion compartment (f00137c)
  • Skip OPENCLOSE SetValue when value unchanged, avoiding unnecessary handler triggers (1c1b915)
  • Add verb condition to non-error branch (bbdb760)

Deadlock & Hang Prevention

  • Fixed permanent hang in hook DLL's singleton initialization under concurrent access (99cad69)
  • Improved error handling in keyboard state queries (169288d)

New Features

  • --get-keyboard option to query current keyboard state (2fae7de)
  • -l/--list to enumerate installed input methods (028ba8c)
  • -h/--help for CLI usage (5404ba8)

Documentation

  • Added comprehensive User Guide covering all commands, Weasel compartment guidance, and OS compatibility notes (6a669cf)

Companion Changes

Project Branch Change
Weasel (RIME) im-control CONVERSION handler rewritten to be value-driven; decoupled from OPENCLOSE; re-entry guard added
vim-im-select im-control im_control_set_mode() writes only CONVERSION, drops -k open — two compartments fully decoupled
AppIME.ahk local AutoHotkey script using im-control for per-app IME auto-switch to English

VimWei added 16 commits June 20, 2026 01:36
- Add `-g`/`--get-keyboard` CLI flag to query and display current
keyboard open/close and alphanumeric/native state
- Query TF compartment values directly in the hook to retrieve live
keyboard state
- Output keyboard state as "open native", "open alphanumeric", or
"close" to stdout or file
- Fix typo in usage string: "alphamumeric" to "alphanumeric"
- use fprintln(outfile, ...) instead of println(...) for the -o file
output
- Add type checks for VARIANT values before accessing keyboard open/close and conversion mode
- Add new ERR_QUERY_KEYBOARD_STATE error code for query failures
- Change --get-keyboard to use VERB_CURRENT instead of VERB_SWITCH
- Add error reporting when fopen fails on the output file
- Intentionally preserve prior error when keyboard state retrieval fails
Replace CreateFileMapping singleton guard with named mutex,
add timeout to event wait, and reset stale event state.

1. Mutex (Local\IMControlMutex) replaces file mapping as singleton
   guard — auto-released when process dies, no residual blocking.
2. WaitForSingleObject timeout: INFINITE → 10000ms; on timeout
   returns ERR_SEND_MESSAGE_TIMEOUT_TIMED_OUT instead of hanging.
3. ResetEvent after CreateEventA to clear leftover signaled state
   from residual hook DLLs in target processes.
4. Bump version to 0.5.1.
- Check vt == VT_I4 || VT_UI4 before reading lVal from GetValue result,
  defaulting oldMode to 0 when the compartment is empty (S_FALSE) to
  avoid reading uninitialized memory
- Set vt = VT_I4 before SetValue to ensure the variant type is correct
  even when GetValue returned S_FALSE and left vt as VT_EMPTY
Always calling SetValue on GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, even
when the value is the same, triggers Weasel's OPENCLOSE handler
(blind toggle of ascii_mode when _isToOpenClose=false). This causes
spurious mode flips on every invocation that includes -k open.

Read current value via GetValue and only call SetValue when it
differs, matching the existing logic for the CONVERSION compartment.
Windows 11 TSF only triggers ITfCompartmentEventSink::OnChange for
writes from activated clients (non-zero TfClientId). The hook was
using TF_CLIENTID_NULL (0) in SetValue calls, so OnChange was never
fired on Windows 11 — WeaselTSF's CONVERSION handler never executed
for external compartment writes.

Call ITfThreadMgr::Activate to obtain a valid TfClientId, use it in
all SetValue calls, and Deactivate when done.
…Value

Log TfClientId from ITfThreadMgr::Activate, and old/new mode +
HRESULT for CONVERSION compartment SetValue. Use DebugView to
diagnose Windows 11 OnChange behavior.
Replace OutputDebugStringW with LOG_INFO/LOG_ERROR for Activate and
CONVERSION SetValue. Enable logInit for hook32 (was commented out).
Logs go to %LOCALAPPDATA%\im-control\hook32.log and hook64.log.
On Windows 11, CoCreateInstance(CLSID_TF_ThreadMgr) returns a NEW
ThreadMgr instance instead of the per-thread singleton. Compartment
writes on the new instance don't trigger OnChange notifications on
sinks (like WeaselTSF's) registered on the framework-provided singleton.

TF_GetThreadMgr is an undocumented-but-stable export from msctf.dll
that reliably returns the per-thread ThreadMgr singleton on both
Windows 10 and 11. Replace both CoCreateInstance(CLSID_TF_ThreadMgr)
calls (get-keyboard-state path and verb-switch path) with
GetThreadMgrSingleton().

Debug logs confirmed: im-control SetValue succeeded (hr=S_OK) but
WeaselTSF _HandleCompartment never fired on Windows 11.
Record the ThreadMgr pointer and thread ID from TF_GetThreadMgr to
compare with WeaselTSF's ActivateEx pThreadMgr pointer.
The w mode overwrites the log on every im-control invocation, making
it impossible to see historical calls. Use append mode to preserve
all invocations for debugging.
Remove verbose LOG_INFO calls for TF_GetThreadMgr pointer, Activate
clientId, and CONVERSION SetValue details. Restore log file open mode
to w (truncate) as per original behavior.
Add section on Win11 TSF differences (TF_GetThreadMgr, TfClientId),
guidance on decoupling CONVERSION and OPENCLOSE when used with Weasel,
and link to the full fix analysis document.
@VimWei

VimWei commented Jul 9, 2026

Copy link
Copy Markdown
Author

关闭了之前的两个PR,重新提交完整的PR。

PS:

  1. 在 windows 10 和 windows 11 下,配合 RIME Weasel 做了测试,但并未测试其他拼音输入法。
  2. 相关的 RIME PR: fix: respond to TSF conversion mode compartment changes rime/weasel#1889

- Read registry ToggleImeOnOpenClose at DllMain to detect Win10 (yes)
  vs Win11 (no) OPENCLOSE handler behavior
- After writing CONVERSION compartment, only reopen OPENCLOSE when
  g_isToOpenClose is true and keyboard is currently closed
- Skip reopen on Win11 (ToggleImeOnOpenClose=no) where OPENCLOSE else
  branch auto-reopens and intervention causes cascade ascii toggle
- Fixes RIME being disabled when gvim exits insert mode with English
  active on Windows 10
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.

1 participant