<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>~iany/ X</title><link>https://blog.iany.me/zh/tags/x/</link><description>X的最新内容 «~iany/»</description><language>zh-CN</language><managingEditor>me@iany.me (Ian Yang)</managingEditor><webMaster>me@iany.me (Ian Yang)</webMaster><copyright>CC-BY-SA 4.0</copyright><lastBuildDate>Wed, 23 Sep 2026 00:45:26 +0800</lastBuildDate><atom:link href="https://blog.iany.me/zh/tags/x/index.xml" rel="self" type="application/rss+xml"/><item><title>让 Windows 输入法的语音快捷键在英文键盘下也能触发</title><link>https://blog.iany.me/zh/2026/09/trigger-windows-ime-voice-input-from-an-english-keyboard-layout/</link><pubDate>Wed, 23 Sep 2026 00:45:26 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/zh/2026/09/trigger-windows-ime-voice-input-from-an-english-keyboard-layout/</guid><description>&lt;p&gt;我平时用英文键盘布局写代码，想说句话就得先切到中文输入法。我希望按 &lt;kbd&gt;RAlt&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt; 就能直接开始语音输入。&lt;/p&gt;
&lt;p&gt;一开始我以为输入法的语音快捷键只在中文布局下才生效。后来发现，微信输入法只要激活过，就会注册一个真正的全局快捷键，切回英文也能触发。但这时它没有权限往当前窗口插入文本，只能把识别的文字放到剪贴板，还得自己粘贴一下。&lt;/p&gt;
&lt;p&gt;我本来就在用 AutoHotkey，比如之前分享过&lt;a href="https://blog.iany.me/zh/2024/04/resolve-the-annoying-shift-space-shortcut-in-windows-chinese-input-methods/"&gt;解决 Windows 中文输入法下 Shift + Space 快捷键的困扰&lt;/a&gt;。于是想让脚本先切到中文，再转发语音快捷键。但是微信输入法的全局快捷键优先级比 AutoHotkey 高。&lt;/p&gt;
&lt;p&gt;解决办法是把两组快捷键分开：&lt;kbd&gt;RAlt&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt; 留给 AutoHotkey，微信输入法改用 &lt;kbd&gt;RWin&lt;/kbd&gt; + &lt;kbd&gt;RAlt&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt;。平时只按前一组，切换布局和转发都交给脚本。&lt;/p&gt;
&lt;h2 id="tldr"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;先把微信输入法里「开启和结束语音输入」的快捷键改成 &lt;kbd&gt;RWin&lt;/kbd&gt; + &lt;kbd&gt;RAlt&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt;。&lt;/p&gt;
&lt;p&gt;下面的 AutoHotkey v2 脚本的效果是：英文布局下按 &lt;kbd&gt;RAlt&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt;，自动切到中文输入法并触发语音；再按一次结束，等语音面板关闭后切回英文。原本就在中文布局下，则只转发开启和结束的快捷键，不切换布局。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ahk"&gt;$&amp;gt;!Space::{
static panel := &amp;quot;语音输入 ahk_class wetype.flutter.setting&amp;quot;
static restoreHKL := 0
ending := WinExist(panel)
KeyWait &amp;quot;Space&amp;quot;
KeyWait &amp;quot;RAlt&amp;quot;
if ending {
Send &amp;quot;{RAlt down}{RWin down}{Space}{RWin up}{RAlt up}&amp;quot;
if WinWaitClose(panel, , 3) {
if restoreHKL
ApplyHKL(restoreHKL)
restoreHKL := 0
}
return
}
restoreHKL := 0
prev := ForegroundHKL()
if !ChineseIMEActive() {
ActivateIME(&amp;quot;00000804&amp;quot;)
deadline := A_TickCount + 1000
while !ChineseIMEActive() &amp;amp;&amp;amp; A_TickCount &amp;lt; deadline
Sleep 20
if !ChineseIMEActive()
return
if (prev &amp;amp; 0x3FF) = 0x09
restoreHKL := prev
}
Sleep 100
Send &amp;quot;{RAlt down}{RWin down}{Space}{RWin up}{RAlt up}&amp;quot;
}
ChineseIMEActive() =&amp;gt; (ForegroundHKL() &amp;amp; 0xFFFF) = 0x0804
ForegroundHKL() {
hwnd := DllCall(&amp;quot;GetForegroundWindow&amp;quot;, &amp;quot;ptr&amp;quot;)
tid := DllCall(&amp;quot;GetWindowThreadProcessId&amp;quot;, &amp;quot;ptr&amp;quot;, hwnd, &amp;quot;ptr&amp;quot;, 0, &amp;quot;uint&amp;quot;)
return DllCall(&amp;quot;GetKeyboardLayout&amp;quot;, &amp;quot;uint&amp;quot;, tid, &amp;quot;ptr&amp;quot;)
}
ActivateIME(klid) {
if hkl := DllCall(&amp;quot;LoadKeyboardLayout&amp;quot;, &amp;quot;str&amp;quot;, klid, &amp;quot;uint&amp;quot;, 0x101, &amp;quot;ptr&amp;quot;)
ApplyHKL(hkl)
}
ApplyHKL(hkl) {
hwnd := DllCall(&amp;quot;GetForegroundWindow&amp;quot;, &amp;quot;ptr&amp;quot;)
tid := DllCall(&amp;quot;GetWindowThreadProcessId&amp;quot;, &amp;quot;ptr&amp;quot;, hwnd, &amp;quot;ptr&amp;quot;, 0, &amp;quot;uint&amp;quot;)
me := DllCall(&amp;quot;GetCurrentThreadId&amp;quot;, &amp;quot;uint&amp;quot;)
DllCall(&amp;quot;AttachThreadInput&amp;quot;, &amp;quot;uint&amp;quot;, me, &amp;quot;uint&amp;quot;, tid, &amp;quot;int&amp;quot;, 1)
DllCall(&amp;quot;ActivateKeyboardLayout&amp;quot;, &amp;quot;ptr&amp;quot;, hkl, &amp;quot;uint&amp;quot;, 0)
DllCall(&amp;quot;AttachThreadInput&amp;quot;, &amp;quot;uint&amp;quot;, me, &amp;quot;uint&amp;quot;, tid, &amp;quot;int&amp;quot;, 0)
PostMessage 0x0050, 0, hkl, , &amp;quot;ahk_id &amp;quot; hwnd
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="两组快捷键分工"&gt;两组快捷键分工&lt;/h2&gt;
&lt;p&gt;&lt;kbd&gt;RAlt&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt; 是给自己按的，&lt;kbd&gt;RWin&lt;/kbd&gt; + &lt;kbd&gt;RAlt&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt; 是给脚本发的。这样脚本就有机会先处理布局，再通知微信输入法。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ahk"&gt;KeyWait &amp;quot;Space&amp;quot;
KeyWait &amp;quot;RAlt&amp;quot;
Send &amp;quot;{RAlt down}{RWin down}{Space}{RWin up}{RAlt up}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先等手上的按键松开，再完整发送另一组组合键，避免物理按键和模拟按键混在一起。&lt;/p&gt;
&lt;p&gt;中文状态下也不能再用 &lt;code&gt;#HotIf&lt;/code&gt; 跳过整个热键了，因为微信输入法已经不认原来的组合键。现在中英文都由脚本接管，只在需要时切换布局。&lt;/p&gt;
&lt;h2 id="切换输入法"&gt;切换输入法&lt;/h2&gt;
&lt;p&gt;切换用三件套。&lt;code&gt;LoadKeyboardLayout&lt;/code&gt; 拿到 HKL，再通过 &lt;code&gt;AttachThreadInput&lt;/code&gt; 把自己挂到前台线程上、调 &lt;code&gt;ActivateKeyboardLayout&lt;/code&gt;，最后补一个 &lt;code&gt;WM_INPUTLANGCHANGEREQUEST&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ahk"&gt;DllCall(&amp;quot;AttachThreadInput&amp;quot;, &amp;quot;uint&amp;quot;, me, &amp;quot;uint&amp;quot;, tid, &amp;quot;int&amp;quot;, 1)
DllCall(&amp;quot;ActivateKeyboardLayout&amp;quot;, &amp;quot;ptr&amp;quot;, hkl, &amp;quot;uint&amp;quot;, 0)
DllCall(&amp;quot;AttachThreadInput&amp;quot;, &amp;quot;uint&amp;quot;, me, &amp;quot;uint&amp;quot;, tid, &amp;quot;int&amp;quot;, 0)
PostMessage 0x0050, 0, hkl, , &amp;quot;ahk_id &amp;quot; hwnd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ActivateKeyboardLayout&lt;/code&gt; 单独调只对自己的线程生效，所以必须先 &lt;code&gt;AttachThreadInput&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;要注意 HKL 层面分不出具体是哪个中文输入法：微软拼音、微信输入法、豆包的 langid 都是 &lt;code&gt;0x0804&lt;/code&gt;，切过去的是上次用的那个。所以判断是否已经是中文，比较低 16 位就够了：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ahk"&gt;ChineseIMEActive() =&amp;gt; (ForegroundHKL() &amp;amp; 0xFFFF) = 0x0804
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;切换是异步的，&lt;code&gt;PostMessage&lt;/code&gt; 发完不代表已经生效，所以要轮询等到真的切过去。脚本最多等一秒，没切成功就直接返回，不再发送语音快捷键；成功后再留 100 毫秒缓冲。&lt;/p&gt;
&lt;h2 id="语音结束后切回英文"&gt;语音结束后切回英文&lt;/h2&gt;
&lt;p&gt;既然是英文布局下触发的，说完话自然应该回到英文。现在用同一个快捷键开启和结束，就可以在第二次按键时处理切回，不用从开始录音起一直等着。&lt;/p&gt;
&lt;p&gt;微信输入法的语音面板是一个独立的顶层窗口。每次按键先用 &lt;code&gt;WinExist&lt;/code&gt; 看它在不在：不在就是开始，在就是结束。找窗口类名的办法是写个脚本记录一段时间内新出现的所有窗口，然后去触发一次语音：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class=wetype.flutter.setting
title=语音输入
exe=wetype_update.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开始时用 &lt;code&gt;static restoreHKL&lt;/code&gt; 记住要恢复的布局，但只记英文：&lt;code&gt;(prev &amp;amp; 0x3FF) = 0x09&lt;/code&gt; 判断的是主语言 ID，不限于某一种英文布局。原本就是中文则不记录，也就不会在结束后多切一次。&lt;/p&gt;
&lt;p&gt;结束时先发快捷键，再等面板关闭：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ahk"&gt;if ending {
Send &amp;quot;{RAlt down}{RWin down}{Space}{RWin up}{RAlt up}&amp;quot;
if WinWaitClose(panel, , 3) {
if restoreHKL
ApplyHKL(restoreHKL)
restoreHKL := 0
}
return
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最多等三秒。面板还没关就不切，避免在语音还没结束时抢先换掉输入法。这个版本只在再次按 &lt;kbd&gt;RAlt&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt; 结束时恢复布局；如果用鼠标关闭面板，不会自动切回。&lt;/p&gt;
&lt;p&gt;类名和标题是从微信输入法 2.1.4.6 抓的，升级后可能变。真变了，脚本就分不清是在开启还是结束语音，自动切回也会失效，需要重新确认窗口信息。&lt;/p&gt;</description><category domain="https://blog.iany.me/zh/post/">文章</category><category domain="https://blog.iany.me/zh/tags/automation/">Automation</category><category domain="https://blog.iany.me/zh/tags/ime/">IME</category><category domain="https://blog.iany.me/zh/tags/shortcut/">Shortcut</category><category domain="https://blog.iany.me/zh/tags/windows/">Windows</category><category domain="https://blog.iany.me/zh/tags/x/">X</category><category domain="https://blog.iany.me/zh/tags/zettel/permanent/">Zettel Permanent</category></item><item><title>解决 Windows 中文输入法下 Shift + Space 快捷键的困扰</title><link>https://blog.iany.me/zh/2024/04/resolve-the-annoying-shift-space-shortcut-in-windows-chinese-input-methods/</link><pubDate>Wed, 10 Apr 2024 19:32:40 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/zh/2024/04/resolve-the-annoying-shift-space-shortcut-in-windows-chinese-input-methods/</guid><description>&lt;p&gt;最近我把主要的工作环境转移到了Windows系统。之前 Windows 最困扰我的是 &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt; 的快捷键。在中文输入法状态下稍微打字快一点，就会误触发切换半角全角的功能，打乱输入的节奏。既然我决定长期使用 Windows 系统，就花了一些时间全面解决了这个问题。&lt;/p&gt;
&lt;h2 id="tldr"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;安装 &lt;a href="https://www.autohotkey.com/"&gt;AutoHotkey&lt;/a&gt; v2 版。将以下代码加入到一个 &lt;code&gt;.ahk&lt;/code&gt; 为后缀的文件中，比如“文档”目录中的 &lt;code&gt;main.ahk&lt;/code&gt; 文件:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;+Space::Send (A_PriorKey = &amp;quot;LShift&amp;quot; ? &amp;quot;+{Space}&amp;quot; : &amp;quot;{Blind}{Shift up}{Space}{Shift down}{LWin}&amp;quot;)
&amp;gt;+Space::Send (A_PriorKey = &amp;quot;RShift&amp;quot; ? &amp;quot;+{Space}&amp;quot; : &amp;quot;{Blind}{Shift up}{Space}{Shift down}{LWin}&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;双击文件 &lt;code&gt;main.ahk&lt;/code&gt; 即可生效，如果要启动时自动生效，可以为这个文件创建一个快捷方式，然后将快捷方式移到到“启动”目录中。可以通过以下办法快速打开“启动”目录。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用快捷键 &lt;kbd&gt;Win&lt;/kbd&gt; + &lt;kbd&gt;r&lt;/kbd&gt; 打开运行窗口&lt;/li&gt;
&lt;li&gt;输入 &lt;code&gt;shell:startup&lt;/code&gt; 并使用 &lt;kbd&gt;Enter&lt;/kbd&gt; 确认&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="思路说明"&gt;思路说明&lt;/h2&gt;
&lt;p&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt; 烦人的地方在于它会吞掉本想输入的空格。输入需要 &lt;kbd&gt;Shift&lt;/kbd&gt; 组合键的符号时，通常在前或者在后要加上空格，比如 &lt;code&gt;:(){}&lt;/code&gt; 这些符号。如果输入速度很快，很可能会在 &lt;kbd&gt;Shift&lt;/kbd&gt; 尚未完全松开时按下 &lt;kbd&gt;Space&lt;/kbd&gt; ，这时候没有输入空格反而是切换了半角全角，如果当前需要使用半角，还得再按一次切换回来。&lt;/p&gt;
&lt;p&gt;基于上述分析，我想实现在按下 &lt;kbd&gt;Shift&lt;/kbd&gt; 键后，如果在按下空格键之前按下了其他键，便视为 &lt;kbd&gt;Shift&lt;/kbd&gt; 键没有及时松开，应该输入一个空格符号。如果在按下 &lt;kbd&gt;Shift&lt;/kbd&gt; 键后立即按下空格键，应保留原来的半角全角切换功能。这样的配置不但能避免误触发，还可以被利用来快速输入 &lt;code&gt;) {&lt;/code&gt; 这样的序列，因为全程 &lt;kbd&gt;Shift&lt;/kbd&gt; 不用松开了。&lt;/p&gt;
&lt;p&gt;上面的 AutoHotkey 代码即实现了这里描述的功能。&lt;/p&gt;
&lt;h2 id="代码说明"&gt;代码说明&lt;/h2&gt;
&lt;p&gt;代码有两行，分别对应的左右手 &lt;kbd&gt;Shift&lt;/kbd&gt; 触发的情况。我尝试过合并成一条但失败了，具体原因我也不清楚，也不知道为什么分开就能工作了。&lt;/p&gt;
&lt;p&gt;下面以左手的第一行为例说明：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;+Space::Send (A_PriorKey = &amp;quot;LShift&amp;quot; ? &amp;quot;+{Space}&amp;quot; : &amp;quot;{Blind}{Shift up}{Space}{Shift down}{LWin}&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AutoHotkey 代码&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;+Space::&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;为 &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt; 设置命令，并只有使用左手的 &lt;kbd&gt;Shift&lt;/kbd&gt; 才能触发。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Send&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;发送指定的按键序列。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(A_PriorKey = &amp;quot;LShift&amp;quot; ? X : Y)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;如果变量 &lt;code&gt;A_PriorKey&lt;/code&gt; 的值等于 &lt;code&gt;LShift&lt;/code&gt; 时返回 X 否则返回 Y。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;+{Space}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;发送组合按键 &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt; 以触发其原本的功能，即切换半角全角。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{Blind}{Shift up}{Space}{Shift down}{LWin}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;这么一大串其实只是为了实现在 AutoHotkey 里在 &lt;kbd&gt;Shfit&lt;/kbd&gt; 按下的情况下输入一个空格符号。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;第一个比较重要的点是自动变量 &lt;a href="https://www.autohotkey.com/docs/v2/Variables.htm#PriorHotkey"&gt;A_PriorHotkey&lt;/a&gt;。它记录的是组合键所有键被按下之前上一个按键状态有变化的键。因为组合键最后一个按下的一定是非修饰键，所以如果按下 &lt;kbd&gt;Shift&lt;/kbd&gt; 后立即按下 &lt;kbd&gt;Space&lt;/kbd&gt;，&lt;code&gt;A_PriorHotkey&lt;/code&gt; 就是 &lt;kbd&gt;LShift&lt;/kbd&gt; 或者 &lt;kbd&gt;RShift&lt;/kbd&gt;。相反，如果 &lt;code&gt;A_PriorHotkey&lt;/code&gt; 等于其它值就表示按下 &lt;kbd&gt;Shift&lt;/kbd&gt; 到按下 &lt;kbd&gt;Space&lt;/kbd&gt; 之间有按过其它按键。&lt;/p&gt;
&lt;p&gt;第二个点是用来模拟输入空格的一长串 &lt;code&gt;{Blind}{Shift up}{Space}{Shift down}{LWin}&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{Blind}&lt;/code&gt; 的作用是让 &lt;code&gt;{Shift up}&lt;/code&gt; 强制修改 &lt;kbd&gt;Shift&lt;/kbd&gt; 的状态改为松开保证后面 &lt;code&gt;{Space}&lt;/code&gt; 输入一个空格符号。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{Shift down}&lt;/code&gt; 是恢复 &lt;kbd&gt;Shift&lt;/kbd&gt; 按下的状态，这样后面可以继续输入还 &lt;kbd&gt;Shift&lt;/kbd&gt; 的组合键。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{LWin}&lt;/code&gt; 不会触发任何操作。它的作用是如果在中文输入法里启用了点按 &lt;kbd&gt;Shift&lt;/kbd&gt; 来切换中英文，在下面的按键序列中会正确输入 &lt;code&gt;: &lt;/code&gt; (冒号+空格) 并且不会误触发中英文切换：
&lt;ul&gt;
&lt;li&gt;按下 &lt;kbd&gt;Shift&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;按下 &lt;kbd&gt;:&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;松开 &lt;kbd&gt;:&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;按下 &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;松开 &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;松开 &lt;kbd&gt;Shift&lt;/kbd&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/zh/post/">文章</category><category domain="https://blog.iany.me/zh/tags/ime/">IME</category><category domain="https://blog.iany.me/zh/tags/shortcut/">Shortcut</category><category domain="https://blog.iany.me/zh/tags/windows/">Windows</category><category domain="https://blog.iany.me/zh/tags/x/">X</category><category domain="https://blog.iany.me/zh/tags/zettel/permanent/">Zettel Permanent</category></item></channel></rss>