












#Requires AutoHotkey v2.0
CoordMode "Mouse", "Screen"
CoordMode "ToolTip", "Screen"
; 热键绑定:F8 开始录制,F9 结束并保存,Esc 退出脚本
F8::StartRecord()
F9::StopRecord()
Esc::ExitApp
; 全局状态
global isRecording := false
global records := []
; 启动录制
StartRecord() {
global isRecording, records
records := []
isRecording := true
ToolTip("⏺️ 正在录制鼠标点击... (按 F9 保存并结束)`n已捕获: 0 步", 20, 20)
}
; 停止录制并保存至 mouse.txt
StopRecord() {
global isRecording, records
if !isRecording {
ToolTip()
return
}
isRecording := false
ToolTip()
if (records.Length = 0) {
MsgBox("未检测到任何鼠标点击动作,未保存文件。", "提示", "Icon!")
return
}
outputFile := "mouse.txt"
fileContent := ""
; 逐行生成 x,y,间隔时间
for index, item in records {
if (index < records.Length) {
; 计算与下一次点击的真实时间间隔 (毫秒)
interval := records[index + 1].tick - item.tick
} else {
; 最后一次点击没有后续点击,默认设为 1000 毫秒
interval := 1000
}
fileContent .= item.x . "," . item.y . "," . interval . "`r`n"
}
; 写入文件(UTF-8 编码,覆盖旧文件)
try {
if FileExist(outputFile)
FileDelete(outputFile)
FileAppend(fileContent, outputFile, "UTF-8")
MsgBox("录制成功!共记录 " . records.Length . " 步,已保存至 " . outputFile, "完成", "Iconi")
} catch as err {
MsgBox("写入文件失败: " . err.Message, "错误", "IconX")
}
}
; 仅在录制状态下监听鼠标左键点击
#HotIf isRecording
~*LButton:: {
global records
; 获取屏幕绝对坐标与当前时间戳
MouseGetPos(&mX, &mY)
records.Push({x: mX, y: mY, tick: A_TickCount})
; 更新悬浮提示
ToolTip(
"⏺️ 正在录制鼠标点击... (按 F9 保存并结束)`n" .
"已捕获: " . records.Length . " 步`n" .
"最新坐标: (" . mX . ", " . mY . ")",
20, 20
)
}
#HotIf
透传点击(~*LButton):
前缀 ~ 确保录制时不拦截正常的鼠标左键功能,系统和应用能正常响应点击。
前缀 * 允许在按住 Shift/Ctrl 等修饰键时依然能触发录制。
高精度时间差:
每次点击时记录系统的微秒计时器 A_TickCount。
第 \(N\) 步的间隔时间计算公式为:records[N+1].tick - records[N].tick。
最后一笔点击由于没有“下一次点击”,默认赋予 1000ms 的安全等待时间。
条件热键(#HotIf isRecording):
仅在按 F8 开启录制后才会挂载鼠标钩子,录制结束后不占用系统监听资源。
#HotIf isRecording 的含义#HotIf 是 AutoHotkey v2 中用于实现条件热键(Context-Sensitive Hotkeys)的指令。
true(真)时生效。#HotIf 的约束力会一直向下延伸,直到遇到下一个 #HotIf 指令(若单独写一行 #HotIf 则表示清除条件,使后续热键全局生效)。#HotIf isRecording
~*LButton:: {
; 只有当 isRecording 为 true 时,按下鼠标左键才会触发此处的逻辑
}
#HotIf ; 闭合条件,恢复默认的全局状态
在未开启录制(isRecording := false)时,系统不会对鼠标左键做任何额外拦截或计算,左键完全由操作系统默认调度,避免了常驻监听带来的性能开销与误触风险。
鼠标钩子底层基于 Windows 系统的 API SetWindowsHookEx(特别是低级鼠标钩子 WH_MOUSE_LL)。
当操作系统产生鼠标事件(如移动、按键按下/弹起、滚轮滚动)时,系统会在把事件投递给目标窗口之前,优先通知 AHK 的钩子函数。AHK 借此决定:
~ 前缀)。通常定义了鼠标热键时 AHK 会自动加载钩子;如果需要强制常驻加载(例如用于记录鼠标历史按键 KeyHistory),可显式调用函数:
InstallMouseHook() ; 安装底层鼠标钩子
| 符号 | 名称 | 作用与示例 |
|---|---|---|
~ |
透传修饰符 | 不阻断原生事件。如 ~LButton::,在执行 AHK 逻辑的同时,目标窗口依然能正常收到点击。 |
* |
通配符 | 忽略修饰键。无论是否同时按住了 Shift、Ctrl 或 Alt,*LButton:: 均可触发。 |
| ` | 符号 | 名称 |
| --- | --- | --- |
~ |
透传修饰符 | 不阻断原生事件。如 ~LButton::,在执行 AHK 逻辑的同时,目标窗口依然能正常收到点击。 |
* |
通配符 | 忽略修饰键。无论是否同时按住了 Shift、Ctrl 或 Alt,*LButton:: 均可触发。 |
| 强制钩子 | 防止死循环。强制走钩子而不是 RegisterHotkey,避免自身发送的按键递归触发自身。 |
|
Up |
按键弹起 | 监听释放瞬间。如 LButton Up::。 |
; 1. 拦截鼠标侧键并改写
XButton1::Send "^c" ; 侧下键变复制
XButton2::Send "^v" ; 侧上键变粘贴
; 2. 结合滚轮实现音量调节(仅在任务栏上方滚动时生效)
#HotIf MouseIsOver("ahk_class Shell_TrayWnd")
WheelUp::SoundSetVolume "+2"
WheelDown::SoundSetVolume "-2"
#HotIf
MouseIsOver(winTitle) {
MouseGetPos ,, &win
return WinExist(winTitle . " ahk_id " . win)
}
WH_MOUSE_LL)有严格的超时限制(通常为几十毫秒)。Sleep),否则会导致整个系统的鼠标出现卡顿、掉帧甚至钩子被系统强制移除。MouseGetPos 或 MouseClick 处理时,务必注意脚本顶部的 CoordMode 设置,保持基准统一。不能把 #HotIf 写在 StartRecord() 函数内部,核心原因在于:#HotIf 是以 # 开头的“预处理/静态指令”(Directive),而不是在代码运行到该行时才执行的“运行时语句”。
# 开头):在脚本刚启动加载(编译/语法解析阶段)时就由解释器静态处理完毕。它用于告诉 AHK 解释器:“下面这几行热键定义,在系统触发时需要挂载一个条件判断”。#HotIf 的作用范围是纯文本从上到下的物理位置,而非函数调用的逻辑时间。如果在函数内部写 #HotIf,AHK v2 会直接抛出语法错误或产生未定义的作用域混乱。整个机制分为两个阶段:启动加载期(静态绑定) 与 按键触发期(动态求值)。
[启动脚本]
│
▼
[解析器自上而下扫描源码]
│
├─► 遇到 F8::StartRecord() ──► 注册全局常规热键
│
├─► 遇到 #HotIf isRecording ──► 记录条件标记:后续热键需绑定 isRecording 条件
│
├─► 遇到 ~*LButton:: ─────────► 安装底层鼠标钩子(WH_MOUSE_LL),并挂上条件检测器
│
└─► 遇到 #HotIf ──────────────► 清除条件标记,恢复全局模式
│
▼
[初始化全局变量] isRecording := false
│
▼
[进入空闲监听循环](此时鼠标左键点击,条件为 false,完全透传,不触发热键函数)
用户物理按下 F8
│
▼
操作系统捕获键盘事件 ──► 投递给 AHK
│
▼
AHK 执行 StartRecord()
│
├─► 清空 records 数组
├─► 变更状态:isRecording := true <─── (仅改变布尔值,不修改任何钩子结构)
└─► 弹出 ToolTip 提示
用户物理按下鼠标左键
│
▼
Windows 操作系统内核产生底层事件 (WM_LBUTTONDOWN)
│
▼
操作系统优先调用 AHK 的低级鼠标钩子函数 (WH_MOUSE_LL)
│
▼
AHK 钩子引擎检索当前热键列表:
│
├─► 匹配到 ~*LButton 定义受 #HotIf isRecording 约束
│
├─► 动态求值:isRecording 当前是 true 还是 false?
│ │
│ ├─► [如果为 false] ──► AHK 忽略此热键,直接把事件放行给被点击的软件
│ │
│ └─► [当前为 true] ──► 判定条件满足!
│
▼
【AHK 执行 ~*LButton 函数体】:
├─► MouseGetPos 获取坐标
├─► records.Push 记录时间戳与坐标
└─► 更新 ToolTip 计数
│
▼
因带有 ~ 修饰符,AHK 把鼠标事件继续放行给目标软件(目标软件正常响应点击)
用户物理按下 F9 ──► 执行 StopRecord()
│
├─► 变更状态:isRecording := false
├─► 计算每一步的实际时间间隔 (Interval)
├─► 写入 mouse.txt 文件
└─► ToolTip() 销毁悬浮提示
如果你希望在 StartRecord() 运行时才去动态创建/开启鼠标热键,不应使用 #HotIf 指令,而必须使用 AHK v2 的 HotIf / Hotkey 内置函数:
; 动态函数方式(无需全局变量与 #HotIf 指令)
StartRecord() {
global records := []
; 动态挂载鼠标左键热键
Hotkey("~*LButton", OnMouseClick, "On")
ToolTip("⏺️ 录制已启动...", 20, 20)
}
StopRecord() {
; 动态卸载鼠标左键热键
Hotkey("~*LButton", "Off")
ToolTip()
; ...保存逻辑...
}
OnMouseClick(thisHotkey) {
MouseGetPos(&mX, &mY)
records.Push({x: mX, y: mY, tick: A_TickCount})
}
#HotIf isRecording:声明式(推荐,代码最简洁,由 AHK 底层在每次点击时自动判断变量)。Hotkey("~*LButton", ...):编程式(在函数内部通过 API 显式开启/关闭钩子)。此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。