Skip to content

Commit 68611d0

Browse files
makerjackieclaude
andcommitted
fix: 修复Shift键切换和输入法延迟问题
主要修复: 1. 支持左右Shift键切换输入法 - 原先只支持左Shift(rawValue == 0x20102) - 现在使用.maskShift标志,支持左右Shift键 2. 修复输入法切换后偶发延迟问题 - 新增forceRefreshInputContext()方法强制刷新输入上下文 - 通过发送系统事件模拟应用切换,确保输入法立即生效 - 解决了图标已切换但输入仍是旧输入法的问题 3. 增强切换稳定性 - 延迟时间从12ms增加到20ms - 为CJKV和非CJKV输入法添加验证和重试机制 - 每次切换后都强制刷新输入上下文 4. 其他改进 - 修复菜单拼写错误:"切换入法" → "切换输入法" - 优化代码结构,提升可维护性 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent add35bb commit 68611d0

2 files changed

Lines changed: 82 additions & 9 deletions

File tree

StatusBarManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class StatusBarManager {
126126

127127
// 修改 Shift 切换选项的文字
128128
let shiftSwitchItem = NSMenuItem(
129-
title: "使用 Shift 切换入法",
129+
title: "使用 Shift 切换输入法",
130130
action: #selector(toggleShiftSwitch),
131131
keyEquivalent: ""
132132
)

inputsource.swift

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,30 +38,74 @@ class InputSource: Equatable {
3838
if self.isCJKV {
3939
switchCJKVSource()
4040
} else {
41+
selectWithRetry()
42+
}
43+
}
44+
45+
// 添加重试机制的切换方法
46+
private func selectWithRetry(maxAttempts: Int = 2) {
47+
for attempt in 1...maxAttempts {
4148
TISSelectInputSource(tisInputSource)
4249
usleep(InputSourceManager.uSeconds)
50+
51+
// 验证切换是否成功
52+
if InputSourceManager.getCurrentSource().id == self.id {
53+
// 强制刷新输入上下文以确保立即生效
54+
InputSourceManager.forceRefreshInputContext()
55+
return
56+
}
57+
58+
// 如果不是最后一次尝试,多等待一段时间再重试
59+
if attempt < maxAttempts {
60+
usleep(InputSourceManager.uSeconds)
61+
}
4362
}
63+
64+
// 即使失败也尝试刷新一次
65+
InputSourceManager.forceRefreshInputContext()
4466
}
4567

4668
private func switchCJKVSource() {
47-
// 直接切换到目标输入法
69+
// 尝试直接切换到目标输入法
4870
TISSelectInputSource(tisInputSource)
4971
usleep(InputSourceManager.uSeconds)
5072

73+
// 验证切换是否成功
74+
if InputSourceManager.getCurrentSource().id == self.id {
75+
// 即使切换成功,也强制刷新输入上下文以确保立即生效
76+
InputSourceManager.forceRefreshInputContext()
77+
return
78+
}
79+
5180
// 如果切换失败,尝试通过非 CJKV 输入法中转
52-
if InputSourceManager.getCurrentSource().id != self.id,
53-
let nonCJKV = InputSourceManager.nonCJKVSource() {
81+
if let nonCJKV = InputSourceManager.nonCJKVSource() {
5482
TISSelectInputSource(nonCJKV.tisInputSource)
5583
usleep(InputSourceManager.uSeconds)
5684
TISSelectInputSource(tisInputSource)
85+
usleep(InputSourceManager.uSeconds)
86+
87+
// 再次验证
88+
if InputSourceManager.getCurrentSource().id == self.id {
89+
// 强制刷新输入上下文
90+
InputSourceManager.forceRefreshInputContext()
91+
return
92+
}
93+
94+
// 最后一次尝试:等待更长时间再切换
95+
usleep(InputSourceManager.uSeconds * 2)
96+
TISSelectInputSource(tisInputSource)
97+
usleep(InputSourceManager.uSeconds)
98+
99+
// 最后也强制刷新一次
100+
InputSourceManager.forceRefreshInputContext()
57101
}
58102
}
59103
}
60104

61105
// 修改 InputSourceManager 类
62106
class InputSourceManager {
63107
static var inputSources: [InputSource] = []
64-
static var uSeconds: UInt32 = 12000
108+
static var uSeconds: UInt32 = 20000 // 增加到20ms以提高稳定性
65109
static var keyboardOnly: Bool = true
66110

67111
static func initialize() {
@@ -135,6 +179,30 @@ class InputSourceManager {
135179
static func getNonCJKVSource() -> InputSource? {
136180
return nonCJKVSource()
137181
}
182+
183+
// 强制刷新输入上下文,确保输入法切换立即生效
184+
// 这个方法模拟用户切换应用的效果,强制macOS刷新当前应用的输入上下文
185+
static func forceRefreshInputContext() {
186+
// 方法1: 发送一个空的键盘事件来触发输入上下文刷新
187+
// 使用 flags changed 事件,这是最轻量级的事件
188+
if let event = CGEvent(keyboardEventSource: nil, virtualKey: 0x3B, keyDown: true) {
189+
// 0x3B 是 Control 键,我们只发送修饰键变化,不会影响用户输入
190+
event.flags = CGEventFlags(rawValue: 0x40101) // Control键的标志
191+
event.post(tap: .cghidEventTap)
192+
usleep(1000) // 1ms延迟
193+
194+
// 立即发送释放事件
195+
if let releaseEvent = CGEvent(keyboardEventSource: nil, virtualKey: 0x3B, keyDown: false) {
196+
releaseEvent.flags = CGEventFlags(rawValue: 0x100)
197+
releaseEvent.post(tap: .cghidEventTap)
198+
}
199+
}
200+
201+
// 方法2: 更激进的方式 - 重新选择当前输入法来强制刷新
202+
let current = TISCopyCurrentKeyboardInputSource().takeRetainedValue()
203+
TISSelectInputSource(current)
204+
usleep(5000) // 5ms延迟确保刷新完成
205+
}
138206
}
139207

140208
// 添加 TISInputSource 扩展
@@ -392,10 +460,15 @@ class KeyboardManager {
392460
// 打印当前修饰键的原始值,用于调试
393461
// print("修饰键 flags 原始值: 0x\(String(flags.rawValue, radix: 16))(\(flags.rawValue))")
394462

395-
// 只有 Shift 键的 flags 值(0x20102 = 131330)
396-
let isShiftKey = flags.rawValue == 0x20102
397-
// Shift 键释放的 flags 值(0x100 = 256)
398-
let isShiftRelease = flags.rawValue == 0x100
463+
// 检测Shift键状态的改进逻辑:支持左右Shift键
464+
// 左Shift: 0x20102, 右Shift: 0x20104
465+
let currentHasShift = flags.contains(.maskShift)
466+
let previousHasShift = lastFlags.contains(.maskShift)
467+
468+
// Shift键按下:当前有Shift但之前没有
469+
let isShiftKey = currentHasShift && !previousHasShift
470+
// Shift键释放:之前有Shift但当前没有
471+
let isShiftRelease = !currentHasShift && previousHasShift
399472

400473
// 检查是否有其他修饰键(当前或之前的状态)
401474
let hasOtherModifiers = flags.contains(.maskCommand) || flags.contains(.maskControl) ||

0 commit comments

Comments
 (0)