Skip to content

[Bug] macOS 26:程序化「切入」鼠鬚管时 input controller 未激活,按键直通为英文(#1140 的对称另一半) #1162

Description

@n374

簡要描述 Bug:

TL;DR (EN): #1140 / #1142 fixed the switch-away half of this macOS 26 regression. The symmetric half appears to remain: when the input source is switched into Squirrel out-of-process via TISSelectInputSource, IMK often fails to activate SquirrelInputController — keystrokes pass straight through as raw ASCII and the input menu renders empty (a placeholder ellipsis instead of Deploy / Sync user data). Switching via the menu bar always works; switching to another app and back immediately restores it. Not 100% deterministic — high probability; please repeat the steps a few times.

#1140 报告的是切走方向(候选框残留、编码不上屏),已由 #1142 修复。本 issue 报告的是同一个 macOS 26 回归的对称另一半切入方向。当由另一个进程调用 TISSelectInputSource 把输入源切鼠鬚管时,IMK 往往不会激活 SquirrelInputController,按键以原始 ASCII 直接上屏。因为 #1140 已关闭且其原始症状确已修复,故另开一条跟踪。

⚠️ 这个问题不是必现,而是高概率触发。 同样的操作偶尔也会一切正常。我暂时没能识别出决定性的附加条件,怀疑与切换时机、前台 App 的焦点状态等因素有关。建议把复现步骤重复 3–5 次,我这边基本都能在几次之内触发。

預期行爲:

由另一进程调用 TISSelectInputSource 切回鼠鬚管后,SquirrelInputController 应被激活,按键进入 Rime 引擎,正常出候选框;输入菜单应显示「重新部署 / 同步用户资料 / 偏好设定…」。走菜单栏原生切换时即是此行为。

實際行爲:

三个互相独立的信号同时出现:

  1. 敲字母无候选框,原始字母直接上屏 —— 按键根本没进 Rime 引擎;
  2. 菜单栏输入法菜单只剩一个省略号占位,正常应有的菜单项全部消失。该菜单由 IMKInputController.menu() 提供,菜单为空说明 IMK 侧没有活动的 input controller —— 这条最能定位问题层次;
  3. 切到别的 App 再切回,立即恢复正常 —— 一次正常的 activate 就能把坏状态冲掉。

此时 TISCopyCurrentKeyboardInputSource() 返回的确实是 im.rime.inputmethod.Squirrel.Hans,即输入源切换本身是成功的,坏的只是激活。

環境

  • 系統版本: macOS 26.5.2 (25F84),Apple Silicon
  • 鼠鬚管版本: Nightly latest,构建于 2026-06-28(commit 2158538),已包含 Finalize stranded composition when input source is switched away programmatically (#1140) #114252d3bc3,2026-06-12 合入)
  • 方案: wubi86(官方 rime-wubi 包,非第三方定制)
  • 使用了 Lua: 否,未使用任何 Lua 脚本
  • 與其它 App 有關: 否。切到别的 App 再切回后原 App 也随之恢复,不是单一 App 的问题

我試過:


复现步骤

  1. 当前输入源为鼠鬚管,输入正常;
  2. 另一个进程调用 TISSelectInputSource 切到 com.apple.keylayout.US
  3. 再由该进程调用 TISSelectInputSource 切回 im.rime.inputmethod.Squirrel.Hans
  4. 立刻在前台 App 里敲字母。

无需 macism / Input Source Pro / Karabiner 等任何第三方工具。下面是一段可直接复制运行的完整脚本(只依赖 Xcode Command Line Tools 提供的 swiftc,若没有请先跑 xcode-select --install):

#!/bin/bash
# rime-switch-repro.sh —— 复现「程序化切入鼠鬚管后 input controller 未激活」
# 用法:bash rime-switch-repro.sh   (可重复运行几次,非必现)
set -euo pipefail

WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT

cat > "$WORK/tis-select.swift" <<'SWIFT'
import Carbon
import Foundation

func inputSource(_ id: String) -> TISInputSource? {
    guard let list = TISCreateInputSourceList(nil, true)?.takeRetainedValue() as? [TISInputSource] else { return nil }
    return list.first {
        guard let p = TISGetInputSourceProperty($0, kTISPropertyInputSourceID) else { return false }
        return (Unmanaged<CFString>.fromOpaque(p).takeUnretainedValue() as String) == id
    }
}

func currentID() -> String {
    guard let s = TISCopyCurrentKeyboardInputSource()?.takeRetainedValue(),
          let p = TISGetInputSourceProperty(s, kTISPropertyInputSourceID) else { return "?" }
    return Unmanaged<CFString>.fromOpaque(p).takeUnretainedValue() as String
}

let target = CommandLine.arguments[1]
guard let src = inputSource(target) else {
    FileHandle.standardError.write("找不到输入源: \(target)\n".data(using: .utf8)!)
    exit(1)
}
let ok = TISSelectInputSource(src) == noErr
usleep(300_000)
print("  select \(target) -> \(ok ? "ok" : "FAIL");  current = \(currentID())")
SWIFT

echo "编译中…"
swiftc -O -o "$WORK/tis-select" "$WORK/tis-select.swift"

RIME="im.rime.inputmethod.Squirrel.Hans"
ABC="com.apple.keylayout.US"

echo
echo "请把光标点进任意输入框(浏览器地址栏、备忘录皆可),然后不要切换窗口。"
echo "10 秒后开始程序化切换。"
for i in $(seq 10 -1 1); do printf "\r  T-%2ds " "$i"; sleep 1; done
echo

echo "切到 U.S. :"
"$WORK/tis-select" "$ABC"
sleep 1
echo "切回鼠鬚管:"
"$WORK/tis-select" "$RIME"

echo
echo "===> 现在立刻敲 a s d f,并观察:"
echo "     1) 有没有候选框?还是原始字母直接上屏?"
echo "     2) 点菜单栏输入法图标,菜单是完整的还是空的?"
echo "     (非必现,若这次正常,请重复运行本脚本几次)"

对照组与排除项

  • 对照组:从菜单栏手动切走再切回鼠鬚管 —— 每次都正常。与 [Bug] macOS 26 Tahoe:程序化切换输入源(TISSelectInputSource)不触发 deactivateServer,候选框残留、composition 不上屏 #1140 中「走菜单栏原生切换 deactivateServer 正常触发」的不对称完全一致。
  • 与按键工具无关:上面的复现脚本不经过 Karabiner / macism 等任何工具,是裸的 TISSelectInputSource 调用。
  • 与其它输入法无关:把同时安装的另一款第三方输入法进程杀掉后,仍然复现。
  • 不是西文模式:Rime 日志显示每次新会话 ascii_mode 均为 reset: 0
  • 引擎侧无异常:Rime 日志无 ERROR(唯一 WARNING 是缺 grammar.yaml,无害);Squirrel 进程存活、无崩溃报告,采样显示主线程在 run loop 正常空转,非死锁。

边界说明(避免过度声称)

  • 没能直接观测到 activateServer 未被调用 —— 上述结论是由三个独立现象共同推出的推断,不是直接证据。(曾尝试用 --getascii 做 controller 活性探针,但对照组不成立:切到 U.S. 后它照样应答,故该探针已作废。)
  • 非必现,见开头的概率说明。
  • 目前只在一台机器上验证过,欢迎其他 macOS 26 用户交叉验证。

一点想法

#1142 的思路是在 kTISNotifySelectedKeyboardInputSourceChanged 里对「切走」做兜底。既然该通知在 out-of-process 切换时是正常派发的(#1140 中已实测),是否可以在同一个 handler 里对称地处理「切入」:当 currentInputSourceIDim.rime.inputmethod.Squirrel 开头时,主动补一次激活?不确定 IMK 侧在 controller 尚未实例化的情况下前端能做到多少,如果方向可行我可以帮忙测试验证。


Disclaimer

本问题由我在日常使用中真实遇到并首先发现;随后的现场取证、对照实验、日志分析与本文撰写,由 Claude Code(Anthropic 的 AI 编程助手)协助完成。文中所有复现步骤与现象均已在我本机实测确认,不是模型凭空推测;但正如「边界说明」一节所述,「activateServer 未被调用」这一层是推断而非直接观测,复现概率也并非 100%。如有表述不准确之处,欢迎直接指出,我可以配合补做实验。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions