鼠标键盘录制 工具

🖱️ Mouse & Keyboard Macro Recorder

支持中英双语

中文界面

exe文件可以在这里下载,https://github.com/kenyucanyou/MouseRecorder

image

English Interface

image

🖱️ 鼠标键盘宏录制工具

A lightweight Mouse and Keyboard Macro Recorder built with AutoIt.
It allows users to record, save, edit, and replay mouse and keyboard actions with precise timing.

这是一个使用 AutoIt 开发的轻量级鼠标键盘宏录制工具
可以 记录、保存、编辑并回放鼠标和键盘操作,并保持精确的时间间隔。


✨ Features | 功能特点

🎥 Mouse Action Recording

鼠标操作录制

Record real-time mouse actions including:

支持记录以下鼠标操作:

  • Mouse movement
  • Left click / Right click
  • Mouse button press & release
  • Precise cursor coordinates

包括:

  • 鼠标移动
  • 左键点击 / 右键点击
  • 鼠标按下与释放
  • 精确坐标记录

⌨ Keyboard Recording

键盘录制

Supports recording common keyboard input:

支持记录常见键盘输入:

  • English letters A-Z
  • Numbers 0-9
  • Enter
  • . (period)

支持:

  • 英文字母 A-Z
  • 数字 0-9
  • 回车键
  • 英文句号

⏱ Precise Timing Replay

精确时间回放

Each action records the exact execution time, allowing playback to reproduce the original user behavior.

每个操作都会记录 精确时间间隔,回放时可以真实还原用户操作节奏。


🔁 Macro Loop Playback

宏循环播放

Macros can be repeated multiple times.

宏可以设置循环执行次数。

Useful for:

适用于:

  • Automated testing
  • Repetitive tasks
  • Game automation
  • Data entry automation

💾 Macro Save & Load

宏文件保存与加载

Recorded macros can be saved to a file and loaded later.

录制的宏可以保存到文件并随时加载。

Macro format:

宏文件格式:

time | action | x | y

Example:

示例:

120|move|520|420
180|left_down|520|420
210|left_up|520|420

🌐 Bilingual Interface

双语界面

The software supports:

软件支持:

  • English interface
  • Chinese interface

方便不同用户使用。


🎛 Visual Macro Timeline

可视化宏时间轴

All recorded actions are displayed in a ListView timeline.

所有操作会显示在 时间轴列表中,包括:

  • Time
  • Action
  • Mouse coordinates

方便查看和调试宏操作。


🚀 Use Cases | 使用场景

This tool is useful for:

该工具适用于:

Automation

  • repetitive office tasks
  • automated mouse operations

Testing

  • GUI automation testing
  • software behavior testing

Productivity

  • repetitive clicking tasks
  • workflow automation

🧠 Technical Highlights

技术亮点

⚡ Lightweight

The compiled executable is very small (< 1MB).

编译后的程序 非常小 (<1MB)


⚡ No Dependencies

No installation required.

无需安装依赖。


⚡ Built with AutoIt

Advantages:

  • native Windows automation
  • fast execution
  • very small binary size

⚡ Efficient Event Recording

Uses high-frequency polling:

AdlibRegister("CaptureMouse", 20)

Captures user input every 20ms for accurate recording.


📦 Example Workflow

使用流程

Start Recording
        ↓
Perform Mouse / Keyboard Actions
        ↓
Stop Recording
        ↓
Save Macro
        ↓
Replay Macro

⌨ Hotkeys | 快捷键

Key Function
F2 Start Recording
F3 Stop Recording
F4 Play Macro
ESC Stop Playback

📂 Macro File Format

宏文件格式

time | action | x | y

Example:

120|move|600|300
180|left_down|600|300
210|left_up|600|300

📜 License

MIT License


⭐ Project Goals

项目目标

Create a simple but powerful automation tool that helps users record and replay repetitive actions efficiently.

创建一个 简单但强大的自动化工具,帮助用户高效完成重复操作。
下面是Autoit的源代码。

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Icon=..\Downloads\ico_mouse_128_28636.ico
#AutoIt3Wrapper_UseX64=y
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <Array.au3>
#include <GuiListView.au3>
#include <GuiStatusBar.au3>
#include <File.au3>
#include <Misc.au3>
#include <ButtonConstants.au3>
#include <FontConstants.au3>

; =========================
;        全局配置
; =========================
Global $g_sLang = "CN" ; 默认中文: CN / 英文: EN

; 新增:播放控制与中止
Global $g_bPlaying = False
Global $g_bAbort = False

; 组件句柄
Global $chkRecordMove, $btnInsertDelay
Global $hGUI, $btnRecord, $btnStop, $btnPlay, $btnSave, $btnLoad, $btnLang
Global $lblMain, $lblLoop, $inputLoop, $List, $hStatus
Global $hListContextMenu, $mnuDelete, $mnuMoveUp, $mnuMoveDown, $mnuClearAll

; 动作数组:5列 [time, type, x, y, label]
;   - time  : 触发相对时间(毫秒)
;   - type  : 原始动作类型(英文如 move/left_down/key_down/delay)
;   - x     : 对鼠标=坐标X;对键盘=VK码;对delay=毫秒
;   - y     : 对鼠标=坐标Y;其余=0
;   - label : 显示说明的“存储值”(语言无关:A、Ctrl、LButton、500ms 等)
Global $aActions[0][5]

; 录制状态
Global $bRecording = False
Global $nStartTime
Global $lastX = -1, $lastY = -1
Global $lastLeft = 0, $lastRight = 0
Global $lastKey[256]

; =========================
;     语言文本映射表
; =========================
Global $g_aLangText = [ _
		["Title", "鼠标键盘宏录制器", "Mouse & Keyboard Macro Recorder"], _
		["MainLabel", "鼠标 & 键盘 宏录制工具", "Mouse & Keyboard Macro Recorder"], _
		["BtnRecord", "开始录制 (F2)", "Start Record (F2)"], _
		["BtnStop", "停止录制 (F3)", "Stop Record (F3)"], _
		["BtnPlay", "播放宏 (F4)", "Play Macro (F4)"], _
		["BtnSave", "保存", "Save"], _
		["BtnLoad", "加载", "Load"], _
		["BtnLang", "中文/EN", "中文/EN"], _
		["LblLoop", "循环次数:", "Loop Count:"], _
		["MenuDelete", "删除选中行 (Del)", "Delete Selected Row (Del)"], _
		["MenuMoveUp", "上移 (Ctrl+↑)", "Move Up (Ctrl+↑)"], _
		["MenuMoveDown", "下移 (Ctrl+↓)", "Move Down (Ctrl+↓)"], _
		["MenuClearAll", "清空全部动作", "Clear All Actions"], _
		["StatusReady", " 就绪", " Ready"], _
		["StatusActionCount", " 动作数:", " Action Count:"], _
		["StatusRecording", " 正在录制... (按 F3 或停止按钮结束)", " Recording... (Press F3 or Stop Button to End)"], _
		["StatusStopRecord", " 已停止录制 共 ", " Recording Stopped Total "], _
		["StatusStopRecord2", " 条动作", " actions"], _
		["StatusPlaying", " 正在播放... (按 ESC 可中止)", " Playing... (Press ESC to Stop)"], _
		["StatusPlayStopped", " 播放已被用户中止", " Play Stopped by User"], _
		["StatusPlayComplete", " 播放完成", " Play Completed"], _
		["StatusDeleted", " 已删除 1 条动作 剩余 ", " Deleted 1 action Remaining "], _
		["StatusMoved", " 动作已移动 当前第 ", " Action Moved Current No. "], _
		["StatusCleared", " 已清空所有动作", " All Actions Cleared"], _
		["StatusSaved", " 已保存到 ", " Saved to "], _
		["StatusLoaded", " 已加载 ", " Loaded "], _
		["MsgStartRecord", "开始录制", "Start Recording"], _
		["MsgStartRecordText", "已开始录制鼠标和键盘操作", "Started recording mouse and keyboard actions"], _
		["MsgStopRecord", "录制结束", "Recording Finished"], _
		["MsgStopRecordText", "录制完成,共记录 ", "Recording completed, total "], _
		["MsgStopRecordText2", " 条动作", " actions recorded"], _
		["MsgNoActions", "提示", "Tip"], _
		["MsgNoActionsText", "当前没有可播放的动作", "No actions to play"], _
		["MsgStartPlay", "开始播放", "Start Playing"], _
		["MsgStartPlayText", "即将开始播放,按 ESC 键可中途停止", "About to start playing, press ESC to stop"], _
		["MsgPlayStopped", "已中止", "Aborted"], _
		["MsgPlayStoppedText", "播放被手动停止", "Play stopped manually"], _
		["MsgPlayComplete", "完成", "Completed"], _
		["MsgPlayCompleteText", "宏播放结束", "Macro play finished"], _
		["MsgConfirmClear", "确认", "Confirmation"], _
		["MsgConfirmClearText", "确定要清空所有录制动作吗?", "Are you sure to clear all recorded actions?"], _
		["DlgSaveTitle", "保存宏", "Save Macro"], _
		["DlgLoadTitle", "打开宏", "Open Macro"], _
		["ActionMove", "移动鼠标", "Mouse Move"], _
		["ActionLBtnDown", "左键按下", "Left Button Down"], _
		["ActionLBtnUp", "左键松开", "Left Button Up"], _
		["ActionRBtnDown", "右键按下", "Right Button Down"], _
		["ActionRBtnUp", "右键松开", "Right Button Up"], _
		["ActionKeyDown", "按键按下", "Key Down"], _
		["ActionKeyUp", "按键松开", "Key Up"], _
		["ActionDelay", "延时", "Delay"], _
		["ColTime", "时间(ms)", "Time(ms)"], _
		["ColAction", "动作类型", "Action Type"], _
		["ColXKey", "X/键码", "X/Key Code"], _
		["ColY", "Y", "Y"], _
		["ColLabel", "键名/说明", "Key/Text"], _
		["ChkRecordMove", "录制鼠标移动", "Record Mouse Move"], _
		["BtnInsertDelay", "插入延时", "Insert Delay"] _
		]

; =========================
;     语言工具函数
; =========================
Func GetLangText($sKey)
	For $i = 0 To UBound($g_aLangText) - 1
		If $g_aLangText[$i][0] = $sKey Then
			If $g_sLang = "CN" Then
				Return $g_aLangText[$i][1]
			Else
				Return $g_aLangText[$i][2]
			EndIf
		EndIf
	Next
	Return $sKey
EndFunc   ;==>GetLangText

Func _VKToName($vk)
	$vk = Number($vk)
	If $vk >= 0x41 And $vk <= 0x5A Then Return Chr($vk) ; A..Z
	If $vk >= 0x30 And $vk <= 0x39 Then Return Chr($vk) ; 0..9

	Switch $vk
		Case 0x1B
			Return "Esc"
		Case 0x0D
			Return "Enter"
		Case 0x09
			Return "Tab"
		Case 0x20
			Return "Space"
		Case 0x08
			Return "Backspace"
		Case 0x2E
			Return "Delete"
		Case 0x24
			Return "Home"
		Case 0x23
			Return "End"
		Case 0x21
			Return "PageUp"
		Case 0x22
			Return "PageDown"
		Case 0x25
			Return "Left"
		Case 0x26
			Return "Up"
		Case 0x27
			Return "Right"
		Case 0x28
			Return "Down"
		Case 0x10
			Return "Shift"
		Case 0x11
			Return "Ctrl"
		Case 0x12
			Return "Alt"
		Case 0x5B
			Return "LWin"
		Case 0x5C
			Return "RWin"
		Case 0xBA
			Return ";"
		Case 0xBB
			Return "="
		Case 0xBC
			Return ","
		Case 0xBD
			Return "-"
		Case 0xBE
			Return "."
		Case 0xBF
			Return "/"
		Case 0xDB
			Return "["
		Case 0xDC
			Return "\"
		Case 0xDD
			Return "]"
		Case 0xDE
			Return "'"
	EndSwitch
	If $vk >= 0x70 And $vk <= 0x87 Then Return "F" & ($vk - 0x6F) ; F1..F24
	Return "VK_" & Hex($vk, 2) ; 回退
EndFunc   ;==>_VKToName

Func _ToDisplayLabel($sActionType, $sStored)
	If $sActionType = "left_down" Or $sActionType = "left_up" Then
		Return ($g_sLang = "CN" ? "左键" : "Left Button")
	ElseIf $sActionType = "right_down" Or $sActionType = "right_up" Then
		Return ($g_sLang = "CN" ? "右键" : "Right Button")
	ElseIf $sActionType = "middle_down" Or $sActionType = "middle_up" Then
		Return ($g_sLang = "CN" ? "中键" : "Middle Button")
	ElseIf $sActionType = "move" Then
		Return ($g_sLang = "CN" ? "移动" : "Move")
	ElseIf $sActionType = "delay" Then
		Return $sStored ; 例如 "500ms"
	EndIf
	Return $sStored ; 键盘/其他:直接显示存储值
EndFunc   ;==>_ToDisplayLabel

; 播放控制:ESC中止 / 安全释放 / 可中断睡眠
Func _AbortPlay()
	$g_bAbort = True
EndFunc   ;==>_AbortPlay

Func _ReleaseSafety()
	MouseUp("left")
	MouseUp("right")
	Local $aMods[5] = [0x10, 0x11, 0x12, 0x5B, 0x5C] ; Shift/Ctrl/Alt/LWin/RWin
	For $i = 0 To UBound($aMods) - 1
		DllCall("user32.dll", "none", "keybd_event", "byte", $aMods[$i], "byte", 0, "dword", 2, "ulong_ptr", 0)
	Next
EndFunc   ;==>_ReleaseSafety

Func _DoEvents()
	Local $msg = GUIGetMsg()
	; 读出让GUI保持响应
EndFunc   ;==>_DoEvents

Func _SleepAbortable($ms)
	Local $t0 = TimerInit()
	While TimerDiff($t0) < $ms
		If $g_bAbort Or _IsPressed("1B") Then Return False
		_DoEvents()
		Sleep(10)
	WEnd
	Return True
EndFunc   ;==>_SleepAbortable

; =========================
;         GUI
; =========================
$hGUI = GUICreate(GetLangText("Title"), 740, 520, -1, -1, BitOR($WS_OVERLAPPEDWINDOW, $WS_CLIPCHILDREN))
GUISetBkColor(0xF5F6F5)

$lblMain = GUICtrlCreateLabel(GetLangText("MainLabel"), 30, 15, 680, 40)
GUICtrlSetFont(-1, 16, $FW_BOLD, 0, "微软雅黑")
GUICtrlSetColor(-1, 0x2C3E50)

Local $btnW1 = 130, $btnW2 = 60, $btnH = 38, $btnY = 65, $gap = 10
$btnRecord = GUICtrlCreateButton(GetLangText("BtnRecord"), 30, $btnY, $btnW1, $btnH)
GUICtrlSetBkColor(-1, 0x27AE60)
GUICtrlSetColor(-1, 0xFFFFFF)
GUICtrlSetFont(-1, 10, $FW_SEMIBOLD, 0, "微软雅黑")

$btnStop = GUICtrlCreateButton(GetLangText("BtnStop"), 30 + $btnW1 + $gap, $btnY, $btnW1, $btnH)
GUICtrlSetBkColor(-1, 0xE74C3C)
GUICtrlSetColor(-1, 0xFFFFFF)
GUICtrlSetFont(-1, 10, $FW_SEMIBOLD, 0, "微软雅黑")

$btnPlay = GUICtrlCreateButton(GetLangText("BtnPlay"), 30 + ($btnW1 + $gap) * 2, $btnY, $btnW1, $btnH)
GUICtrlSetBkColor(-1, 0x3498DB)
GUICtrlSetColor(-1, 0xFFFFFF)
GUICtrlSetFont(-1, 10, $FW_SEMIBOLD, 0, "微软雅黑")

$btnSave = GUICtrlCreateButton(GetLangText("BtnSave"), 30 + ($btnW1 + $gap) * 3, $btnY, $btnW2, $btnH)
GUICtrlSetBkColor(-1, 0x95A5A6)
GUICtrlSetColor(-1, 0xFFFFFF)
GUICtrlSetFont(-1, 10, 400, 0, "微软雅黑")

$btnLoad = GUICtrlCreateButton(GetLangText("BtnLoad"), 30 + ($btnW1 + $gap) * 3 + $btnW2 + $gap, $btnY, $btnW2, $btnH)
GUICtrlSetBkColor(-1, 0x95A5A6)
GUICtrlSetColor(-1, 0xFFFFFF)
GUICtrlSetFont(-1, 10, 400, 0, "微软雅黑")

$btnLang = GUICtrlCreateButton(GetLangText("BtnLang"), 30 + ($btnW1 + $gap) * 3 + ($btnW2 + $gap) * 2, $btnY, $btnW2, $btnH)
GUICtrlSetBkColor(-1, 0x8E44AD)
GUICtrlSetColor(-1, 0xFFFFFF)
GUICtrlSetFont(-1, 10, 400, 0, "微软雅黑")

$lblLoop = GUICtrlCreateLabel(GetLangText("LblLoop"), 30, 115, 80, 24)
GUICtrlSetFont(-1, 10, 400, 0, "微软雅黑")
GUICtrlSetColor(-1, 0x34495E)

$inputLoop = GUICtrlCreateInput("1", 110, 112, 60, 28)
GUICtrlSetFont(-1, 10, 400, 0, "Consolas")
GUICtrlSetBkColor(-1, 0xFFFFFF)

$chkRecordMove = GUICtrlCreateCheckbox(GetLangText("ChkRecordMove"), 200, 115, 160, 24)
GUICtrlSetFont(-1, 10, 400, 0, "微软雅黑")
GUICtrlSetColor(-1, 0x34495E)
GUICtrlSetState(-1, $GUI_UNCHECKED)

$btnInsertDelay = GUICtrlCreateButton(GetLangText("BtnInsertDelay"), 380, 112, 160, 28)
GUICtrlSetFont(-1, 10, 400, 0, "微软雅黑")
GUICtrlSetBkColor(-1, 0xF39C12)
GUICtrlSetColor(-1, 0xFFFFFF)

; === ListView(5列) ===
$List = GUICtrlCreateListView( _
		GetLangText("ColTime") & "|" & _
		GetLangText("ColAction") & "|" & _
		GetLangText("ColXKey") & "|" & _
		GetLangText("ColY") & "|" & _
		GetLangText("ColLabel"), _
		30, 150, 680, 320, _
		BitOR($LVS_REPORT, $LVS_SINGLESEL, $LVS_SHOWSELALWAYS) _
		)
_GUICtrlListView_SetExtendedListViewStyle($List, BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_GRIDLINES, $LVS_EX_DOUBLEBUFFER, $LVS_EX_FLATSB))
_GUICtrlListView_SetBkColor($List, 0xFCFCFC)
_GUICtrlListView_SetTextBkColor($List, 0xFCFCFC)
_GUICtrlListView_SetTextColor($List, 0x2C3E50)
_GUICtrlListView_SetColumnWidth($List, 0, 110)
_GUICtrlListView_SetColumnWidth($List, 1, 180)
_GUICtrlListView_SetColumnWidth($List, 2, 90)
_GUICtrlListView_SetColumnWidth($List, 3, 90)
_GUICtrlListView_SetColumnWidth($List, 4, 140)
GUICtrlSetFont($List, 10, 400, 0, "Consolas")

; 右键菜单
$hListContextMenu = GUICtrlCreateContextMenu($List)
$mnuDelete = GUICtrlCreateMenuItem(GetLangText("MenuDelete"), $hListContextMenu)
$mnuMoveUp = GUICtrlCreateMenuItem(GetLangText("MenuMoveUp"), $hListContextMenu)
$mnuMoveDown = GUICtrlCreateMenuItem(GetLangText("MenuMoveDown"), $hListContextMenu)
GUICtrlCreateMenuItem("", $hListContextMenu)
$mnuClearAll = GUICtrlCreateMenuItem(GetLangText("MenuClearAll"), $hListContextMenu)
GUICtrlSetFont($mnuDelete, 9, 400, 0, "微软雅黑")
GUICtrlSetFont($mnuMoveUp, 9, 400, 0, "微软雅黑")
GUICtrlSetFont($mnuMoveDown, 9, 400, 0, "微软雅黑")
GUICtrlSetFont($mnuClearAll, 9, 400, 0, "微软雅黑")

; 状态栏
$hStatus = _GUICtrlStatusBar_Create($hGUI)
_GUICtrlStatusBar_SetSimple($hStatus, False)
Local $aPartsWidths[2] = [400, -1]
_GUICtrlStatusBar_SetParts($hStatus, $aPartsWidths)
_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusReady"), 0)
_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusActionCount") & "0 ", 1)

GUISetState()

; 热键
HotKeySet("{F2}", "StartRecording")
HotKeySet("{F3}", "StopRecording")
HotKeySet("{F4}", "PlayMacro")
; ESC 在 PlayMacro 播放期间动态注册

; =========================
;       GUI 主循环
; =========================
While 1
	$msg = GUIGetMsg(1)
	Switch $msg[0]
		Case $GUI_EVENT_CLOSE
			Exit
		Case $btnRecord
			StartRecording()
		Case $btnStop
			StopRecording()
		Case $btnPlay
			PlayMacro()
		Case $btnSave
			SaveMacro()
		Case $btnLoad
			LoadMacro()
		Case $btnLang
			SwitchLanguage()
		Case $mnuDelete
			DeleteSelectedItem()
		Case $mnuMoveUp
			MoveSelectedItem(-1)
		Case $mnuMoveDown
			MoveSelectedItem(1)
		Case $btnInsertDelay
			InsertDelay()
		Case $mnuClearAll
			If MsgBox(4 + 32 + 256, GetLangText("MsgConfirmClear"), GetLangText("MsgConfirmClearText")) = 6 Then
				ReDim $aActions[0][5]
				_GUICtrlListView_DeleteAllItems($List)
				_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusCleared"), 0)
				_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusActionCount") & "0 ", 1)
			EndIf
	EndSwitch

	; 快捷键:Delete / Ctrl+Up / Ctrl+Down
	If _IsPressed("2E") Then
		DeleteSelectedItem()
		Sleep(150)
	EndIf
	If _IsPressed("11") Then
		If _IsPressed("26") Then MoveSelectedItem(-1)
		Sleep(120)
		If _IsPressed("28") Then MoveSelectedItem(1)
		Sleep(120)
	EndIf
	Sleep(10)
WEnd

; =========================
;       功能函数
; =========================
Func SwitchLanguage()
	$g_sLang = ($g_sLang = "CN") ? "EN" : "CN"

	WinSetTitle($hGUI, "", GetLangText("Title"))
	GUICtrlSetData($lblMain, GetLangText("MainLabel"))
	GUICtrlSetData($btnRecord, GetLangText("BtnRecord"))
	GUICtrlSetData($btnStop, GetLangText("BtnStop"))
	GUICtrlSetData($btnPlay, GetLangText("BtnPlay"))
	GUICtrlSetData($btnSave, GetLangText("BtnSave"))
	GUICtrlSetData($btnLoad, GetLangText("BtnLoad"))
	GUICtrlSetData($btnLang, GetLangText("BtnLang"))
	GUICtrlSetData($lblLoop, GetLangText("LblLoop"))
	GUICtrlSetData($chkRecordMove, GetLangText("ChkRecordMove"))
	GUICtrlSetData($btnInsertDelay, GetLangText("BtnInsertDelay"))

	; 重建 ListView(5列)
	GUICtrlDelete($List)
	$List = GUICtrlCreateListView( _
			GetLangText("ColTime") & "|" & _
			GetLangText("ColAction") & "|" & _
			GetLangText("ColXKey") & "|" & _
			GetLangText("ColY") & "|" & _
			GetLangText("ColLabel"), _
			30, 150, 680, 320, _
			BitOR($LVS_REPORT, $LVS_SINGLESEL, $LVS_SHOWSELALWAYS) _
			)
	_GUICtrlListView_SetExtendedListViewStyle($List, BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_GRIDLINES, $LVS_EX_DOUBLEBUFFER, $LVS_EX_FLATSB))
	_GUICtrlListView_SetBkColor($List, 0xFCFCFC)
	_GUICtrlListView_SetTextBkColor($List, 0xFCFCFC)
	_GUICtrlListView_SetTextColor($List, 0x2C3E50)
	_GUICtrlListView_SetColumnWidth($List, 0, 110)
	_GUICtrlListView_SetColumnWidth($List, 1, 180)
	_GUICtrlListView_SetColumnWidth($List, 2, 90)
	_GUICtrlListView_SetColumnWidth($List, 3, 90)
	_GUICtrlListView_SetColumnWidth($List, 4, 140)
	GUICtrlSetFont($List, 10, 400, 0, "Consolas")

	; 右键菜单也要重建以绑定到新的List控件
	$hListContextMenu = GUICtrlCreateContextMenu($List)
	$mnuDelete = GUICtrlCreateMenuItem(GetLangText("MenuDelete"), $hListContextMenu)
	$mnuMoveUp = GUICtrlCreateMenuItem(GetLangText("MenuMoveUp"), $hListContextMenu)
	$mnuMoveDown = GUICtrlCreateMenuItem(GetLangText("MenuMoveDown"), $hListContextMenu)
	GUICtrlCreateMenuItem("", $hListContextMenu)
	$mnuClearAll = GUICtrlCreateMenuItem(GetLangText("MenuClearAll"), $hListContextMenu)
	GUICtrlSetFont($mnuDelete, 9, 400, 0, "微软雅黑")
	GUICtrlSetFont($mnuMoveUp, 9, 400, 0, "微软雅黑")
	GUICtrlSetFont($mnuMoveDown, 9, 400, 0, "微软雅黑")
	GUICtrlSetFont($mnuClearAll, 9, 400, 0, "微软雅黑")

	; 重新加载已有动作(语言化显示)
	For $i = 0 To UBound($aActions) - 1
		Local $t = Round($aActions[$i][0], 0)
		Local $sType = $aActions[$i][1]
		Local $sDispType = ""
		Switch $sType
			Case "move"
				$sDispType = GetLangText("ActionMove")
			Case "left_down"
				$sDispType = GetLangText("ActionLBtnDown")
			Case "left_up"
				$sDispType = GetLangText("ActionLBtnUp")
			Case "right_down"
				$sDispType = GetLangText("ActionRBtnDown")
			Case "right_up"
				$sDispType = GetLangText("ActionRBtnUp")
			Case "key_down"
				$sDispType = GetLangText("ActionKeyDown")
			Case "key_up"
				$sDispType = GetLangText("ActionKeyUp")
			Case "delay"
				$sDispType = GetLangText("ActionDelay")
				; 兼容旧中文类型字符串
			Case "移动鼠标"
				$sType = "move" ; 纠正存储
				$sDispType = GetLangText("ActionMove")
			Case "左键按下"
				$sType = "left_down" ; 纠正存储
				$sDispType = GetLangText("ActionLBtnDown")
			Case "左键松开"
				$sType = "left_up"
				$sDispType = GetLangText("ActionLBtnUp")
			Case "右键按下"
				$sType = "right_down"
				$sDispType = GetLangText("ActionRBtnDown")
			Case "右键松开"
				$sType = "right_up"
				$sDispType = GetLangText("ActionRBtnUp")
			Case "按键按下"
				$sType = "key_down"
				$sDispType = GetLangText("ActionKeyDown")
			Case "按键松开"
				$sType = "key_up"
				$sDispType = GetLangText("ActionKeyUp")
		EndSwitch

		Local $sLabelDisp = _ToDisplayLabel($sType, $aActions[$i][4])
		GUICtrlCreateListViewItem( _
				$t & "|" & _
				$sDispType & "|" & _
				$aActions[$i][2] & "|" & _
				$aActions[$i][3] & "|" & _
				$sLabelDisp, _
				$List _
				)
	Next

	; 更新状态栏
	Local $iCount = UBound($aActions)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusReady"), 0)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusActionCount") & $iCount & " ", 1)
EndFunc   ;==>SwitchLanguage

; 录制
Func StartRecording()
	If $bRecording Then Return
	ReDim $aActions[0][5]
	_GUICtrlListView_DeleteAllItems($List)
	For $i = 0 To 255
		$lastKey[$i] = 0
	Next
	$nStartTime = TimerInit()
	$bRecording = True
	AdlibRegister("CaptureMouse", 20)
	AdlibRegister("CaptureKeyboard", 20)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusRecording"), 0)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusActionCount") & "0 ", 1)
	MsgBox(64, GetLangText("MsgStartRecord"), GetLangText("MsgStartRecordText"))
EndFunc   ;==>StartRecording

Func StopRecording()
	If Not $bRecording Then Return
	$bRecording = False
	AdlibUnRegister("CaptureMouse")
	AdlibUnRegister("CaptureKeyboard")
	Local $cnt = UBound($aActions)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusStopRecord") & $cnt & GetLangText("StatusStopRecord2"), 0)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusActionCount") & $cnt, 1)
	MsgBox(64, GetLangText("MsgStopRecord"), GetLangText("MsgStopRecordText") & $cnt & GetLangText("MsgStopRecordText2"))
EndFunc   ;==>StopRecording

Func CaptureMouse()
	If Not $bRecording Then Return
	Local $pos = MouseGetPos()
	Local $x = $pos[0], $y = $pos[1]
	Local $left = _IsPressed("01"), $right = _IsPressed("02")

	If GUICtrlRead($chkRecordMove) = $GUI_CHECKED Then
		If $x <> $lastX Or $y <> $lastY Then
			AddAction("move", $x, $y)
			$lastX = $x
			$lastY = $y
		EndIf
	Else
		$lastX = $x
		$lastY = $y
	EndIf

	If $left And Not $lastLeft Then AddAction("left_down", $x, $y)
	If Not $left And $lastLeft Then AddAction("left_up", $x, $y)
	If $right And Not $lastRight Then AddAction("right_down", $x, $y)
	If Not $right And $lastRight Then AddAction("right_up", $x, $y)

	$lastLeft = $left
	$lastRight = $right
EndFunc   ;==>CaptureMouse

Func CaptureKeyboard()
	If Not $bRecording Then Return
	Static $aHex[256]
	If $aHex[0] = "" Then
		For $i = 0 To 255
			$aHex[$i] = Hex($i, 2)
		Next
	EndIf
	For $i = 1 To 255
		Local $pressed = _IsPressed($aHex[$i])
		If $pressed And Not $lastKey[$i] Then AddAction("key_down", $i, 0)
		If Not $pressed And $lastKey[$i] Then AddAction("key_up", $i, 0)
		$lastKey[$i] = $pressed
	Next
EndFunc   ;==>CaptureKeyboard

Func AddAction($sType, $x, $y)
	Local $time = TimerDiff($nStartTime)
	Local $n = UBound($aActions)
	ReDim $aActions[$n + 1][5]
	$aActions[$n][0] = $time
	$aActions[$n][1] = $sType
	$aActions[$n][2] = $x
	$aActions[$n][3] = $y

	; 第5列:存储值(语言无关)
	Local $sLabelStored = ""
	Switch $sType
		Case "key_down", "key_up"
			$sLabelStored = _VKToName($x)
		Case "left_down", "left_up"
			$sLabelStored = "LButton"
		Case "right_down", "right_up"
			$sLabelStored = "RButton"
		Case "middle_down", "middle_up"
			$sLabelStored = "MButton"
		Case "delay"
			$sLabelStored = $x & "ms"
		Case "move"
			$sLabelStored = "" ; 可留空
	EndSwitch
	$aActions[$n][4] = $sLabelStored

	; 列2:动作类型(本地化)
	Local $sDispType = ""
	Switch $sType
		Case "move"
			$sDispType = GetLangText("ActionMove")
		Case "left_down"
			$sDispType = GetLangText("ActionLBtnDown")
		Case "left_up"
			$sDispType = GetLangText("ActionLBtnUp")
		Case "right_down"
			$sDispType = GetLangText("ActionRBtnDown")
		Case "right_up"
			$sDispType = GetLangText("ActionRBtnUp")
		Case "key_down"
			$sDispType = GetLangText("ActionKeyDown")
		Case "key_up"
			$sDispType = GetLangText("ActionKeyUp")
		Case "delay"
			$sDispType = GetLangText("ActionDelay")
	EndSwitch

	Local $sLabelDisp = _ToDisplayLabel($sType, $sLabelStored)

	GUICtrlCreateListViewItem( _
			Round($time, 0) & "|" & _
			$sDispType & "|" & _
			$x & "|" & _
			$y & "|" & _
			$sLabelDisp, _
			$List _
			)

	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusActionCount") & ($n + 1), 1)
EndFunc   ;==>AddAction

Func InsertDelay()
	Local $iSel = _GUICtrlListView_GetSelectedIndices($List)
	If $iSel = "" Or $iSel = -1 Then
		MsgBox(48, GetLangText("MsgNoActions"), GetLangText("MsgSelectRow"))
		Return
	EndIf
	Local $index = Number($iSel)
	Local $sDelay = InputBox(GetLangText("DlgDelayTitle"), GetLangText("DlgDelayPrompt"), "1000")
	If @error Then Return
	Local $iDelay = Number($sDelay)
	If $iDelay < 1 Then $iDelay = 1000

	Local $newIndex = $index + 1
	_ArrayInsert($aActions, $newIndex)
	ReDim $aActions[UBound($aActions)][5]

	If $newIndex > 0 Then
		$aActions[$newIndex][0] = $aActions[$newIndex - 1][0] + 1
	Else
		$aActions[$newIndex][0] = 0
	EndIf
	$aActions[$newIndex][1] = "delay"
	$aActions[$newIndex][2] = $iDelay
	$aActions[$newIndex][3] = 0
	$aActions[$newIndex][4] = $iDelay & "ms"

	Local $hListView = GUICtrlGetHandle($List)
	Local $dispTime = Round($aActions[$newIndex][0], 0)
	Local $sDispType = GetLangText("ActionDelay")
	Local $sLabelDisp = _ToDisplayLabel("delay", $aActions[$newIndex][4])

	Local $idx = _GUICtrlListView_InsertItem($hListView, $dispTime, $newIndex)
	_GUICtrlListView_AddSubItem($hListView, $idx, $sDispType, 1)
	_GUICtrlListView_AddSubItem($hListView, $idx, $iDelay, 2)
	_GUICtrlListView_AddSubItem($hListView, $idx, "0", 3)
	_GUICtrlListView_AddSubItem($hListView, $idx, $sLabelDisp, 4)

	_GUICtrlListView_SetItemSelected($List, $newIndex, True, True)
	_GUICtrlListView_EnsureVisible($List, $newIndex)

	Local $cnt = UBound($aActions)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusActionCount") & $cnt, 1)
EndFunc   ;==>InsertDelay

Func DeleteSelectedItem()
	Local $iSel = _GUICtrlListView_GetSelectedIndices($List)
	If $iSel = "" Or $iSel = -1 Then Return
	Local $index = Number($iSel)
	_ArrayDelete($aActions, $index)
	_GUICtrlListView_DeleteItem($List, $index)

	Local $count = UBound($aActions)
	If $index >= $count Then $index = $count - 1
	If $index >= 0 Then
		_GUICtrlListView_SetItemSelected($List, $index, True, True)
		_GUICtrlListView_EnsureVisible($List, $index)
	EndIf
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusDeleted") & $count & GetLangText("StatusStopRecord2"), 0)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusActionCount") & $count, 1)
EndFunc   ;==>DeleteSelectedItem

Func MoveSelectedItem($direction)
	Local $iSel = _GUICtrlListView_GetSelectedIndices($List)
	If $iSel = "" Or $iSel = -1 Then Return
	Local $index = Number($iSel)
	Local $newIndex = $index + $direction
	If $newIndex < 0 Or $newIndex >= UBound($aActions) Then Return

	; 交换两行(如果 _ArraySwap 对 2D 不可靠,可替换为手动交换)
	_ArraySwap($aActions, $index, $newIndex)

	_GUICtrlListView_DeleteAllItems($List)
	For $i = 0 To UBound($aActions) - 1
		Local $t = Round($aActions[$i][0], 0)
		Local $sType = $aActions[$i][1]
		Local $sDispType = ""
		Switch $sType
			Case "move"
				$sDispType = GetLangText("ActionMove")
			Case "left_down"
				$sDispType = GetLangText("ActionLBtnDown")
			Case "left_up"
				$sDispType = GetLangText("ActionLBtnUp")
			Case "right_down"
				$sDispType = GetLangText("ActionRBtnDown")
			Case "right_up"
				$sDispType = GetLangText("ActionRBtnUp")
			Case "key_down"
				$sDispType = GetLangText("ActionKeyDown")
			Case "key_up"
				$sDispType = GetLangText("ActionKeyUp")
			Case "delay"
				$sDispType = GetLangText("ActionDelay")
		EndSwitch
		Local $sLabelDisp = _ToDisplayLabel($sType, $aActions[$i][4])
		GUICtrlCreateListViewItem( _
				$t & "|" & _
				$sDispType & "|" & _
				$aActions[$i][2] & "|" & _
				$aActions[$i][3] & "|" & _
				$sLabelDisp, _
				$List _
				)
	Next
	_GUICtrlListView_SetItemSelected($List, $newIndex, True, True)
	_GUICtrlListView_EnsureVisible($List, $newIndex)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusMoved") & ($newIndex + 1) & GetLangText("StatusStopRecord2"), 0)
EndFunc   ;==>MoveSelectedItem

Func SwitchToEnglish()
	DllCall("user32.dll", "long", "LoadKeyboardLayout", "str", "00000409", "int", 1)
EndFunc   ;==>SwitchToEnglish

; 播放(可中止)
Func PlayMacro()
	If $g_bPlaying Then Return
	If UBound($aActions) = 0 Then
		MsgBox(16, GetLangText("MsgNoActions"), GetLangText("MsgNoActionsText"))
		Return
	EndIf

	SwitchToEnglish()

	Local $loop = Number(GUICtrlRead($inputLoop))
	If $loop < 1 Then $loop = 1

	$g_bAbort = False
	$g_bPlaying = True

	; 播放期:绑定 ESC 中止
	HotKeySet("{ESC}", "_AbortPlay")

	; 播放期禁用按钮,避免重入/误触
	GUICtrlSetState($btnRecord, $GUI_DISABLE)
	GUICtrlSetState($btnStop, $GUI_DISABLE)
	GUICtrlSetState($btnPlay, $GUI_DISABLE)
	GUICtrlSetState($btnSave, $GUI_DISABLE)
	GUICtrlSetState($btnLoad, $GUI_DISABLE)
	GUICtrlSetState($btnLang, $GUI_DISABLE)
	GUICtrlSetState($btnInsertDelay, $GUI_DISABLE)
	GUICtrlSetState($chkRecordMove, $GUI_DISABLE)

	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusPlaying"), 0)
	MsgBox(64, GetLangText("MsgStartPlay"), GetLangText("MsgStartPlayText"))

	Local $aborted = False

	For $k = 1 To $loop
		Local $start = TimerInit()

		For $i = 0 To UBound($aActions) - 1
			If $g_bAbort Or _IsPressed("1B") Then
				$aborted = True
				ExitLoop 2
			EndIf

			Local $time = $aActions[$i][0]
			Local $type = $aActions[$i][1]
			Local $x = $aActions[$i][2]
			Local $y = $aActions[$i][3]

			Local $wait = $time - TimerDiff($start)
			If $wait > 0 Then
				If Not _SleepAbortable($wait) Then
					$aborted = True
					ExitLoop 2
				EndIf
			EndIf

			If $g_bAbort Or _IsPressed("1B") Then
				$aborted = True
				ExitLoop 2
			EndIf

			Switch $type
				Case "move", "移动鼠标"
					MouseMove($x, $y, 0)

				Case "left_down", "左键按下"
					MouseMove($x, $y, 0)
					MouseDown("left")

				Case "left_up", "左键松开"
					MouseMove($x, $y, 0)
					MouseUp("left")

				Case "right_down", "右键按下"
					MouseMove($x, $y, 0)
					MouseDown("right")

				Case "right_up", "右键松开"
					MouseMove($x, $y, 0)
					MouseUp("right")

				Case "delay"
					If Not _SleepAbortable($x) Then
						$aborted = True
						ExitLoop 2
					EndIf

				Case "key_down", "按键按下"
					DllCall("user32.dll", "none", "keybd_event", "byte", $x, "byte", 0, "dword", 0, "ulong_ptr", 0)

				Case "key_up", "按键松开"
					DllCall("user32.dll", "none", "keybd_event", "byte", $x, "byte", 0, "dword", 2, "ulong_ptr", 0)
			EndSwitch
		Next
	Next

	; 统一收尾
	_ReleaseSafety()
	HotKeySet("{ESC}") ; 解除 ESC
	GUICtrlSetState($btnRecord, $GUI_ENABLE)
	GUICtrlSetState($btnStop, $GUI_ENABLE)
	GUICtrlSetState($btnPlay, $GUI_ENABLE)
	GUICtrlSetState($btnSave, $GUI_ENABLE)
	GUICtrlSetState($btnLoad, $GUI_ENABLE)
	GUICtrlSetState($btnLang, $GUI_ENABLE)
	GUICtrlSetState($btnInsertDelay, $GUI_ENABLE)
	GUICtrlSetState($chkRecordMove, $GUI_ENABLE)

	If $aborted Then
		_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusPlayStopped"), 0)
		MsgBox(64, GetLangText("MsgPlayStopped"), GetLangText("MsgPlayStoppedText"))
	Else
		_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusPlayComplete"), 0)
		MsgBox(64, GetLangText("MsgPlayComplete"), GetLangText("MsgPlayCompleteText"))
	EndIf

	$g_bPlaying = False
EndFunc   ;==>PlayMacro

; 保存为 TAB 分隔 5 列;每条记录 1 行,更稳健
Func SaveMacro()
	Local $file = FileSaveDialog(GetLangText("DlgSaveTitle"), "", "Macro (*.macro)", 2)
	If @error Then Return
	Local $h = FileOpen($file, 2)
	If $h = -1 Then Return
	For $i = 0 To UBound($aActions) - 1
		Local $line = _
				$aActions[$i][0] & @TAB & _
				$aActions[$i][1] & @TAB & _
				$aActions[$i][2] & @TAB & _
				$aActions[$i][3] & @TAB & _
				$aActions[$i][4]
		FileWriteLine($h, $line)
	Next
	FileClose($h)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusSaved") & $file, 0)
EndFunc   ;==>SaveMacro

; 加载:优先解析 TAB 分隔;若字段数不足,再尝试按 "\n" 字面分隔(兼容旧版)
Func LoadMacro()
	Local $file = FileOpenDialog(GetLangText("DlgLoadTitle"), "", "Macro (*.macro)")
	If @error Then Return
	Local $lines
	_FileReadToArray($file, $lines)
	ReDim $aActions[0][5]
	_GUICtrlListView_DeleteAllItems($List)

	For $i = 1 To $lines[0]
		Local $p = StringSplit($lines[$i], @TAB, 2)
		If UBound($p) < 4 Then
			; 兼容旧格式:尝试用 "\n" 文本分隔
			Local $p2 = StringSplit($lines[$i], "\n", 2)
			If UBound($p2) >= 4 Then
				$p = $p2
			Else
				ContinueLoop
			EndIf
		EndIf

		Local $n = UBound($aActions)
		ReDim $aActions[$n + 1][5]

		$aActions[$n][0] = Number($p[0])
		$aActions[$n][1] = $p[1]
		$aActions[$n][2] = $p[2]
		$aActions[$n][3] = $p[3]
		If UBound($p) >= 5 Then
			$aActions[$n][4] = $p[4]
		Else
			; 旧格式补写
			Switch $aActions[$n][1]
				Case "key_down", "key_up"
					$aActions[$n][4] = _VKToName($aActions[$n][2])
				Case "left_down", "left_up"
					$aActions[$n][4] = "LButton"
				Case "right_down", "right_up"
					$aActions[$n][4] = "RButton"
				Case "middle_down", "middle_up"
					$aActions[$n][4] = "MButton"
				Case "delay"
					$aActions[$n][4] = $aActions[$n][2] & "ms"
				Case Else
					$aActions[$n][4] = ""
			EndSwitch
		EndIf

		Local $sDispType = ""
		Switch $aActions[$n][1]
			Case "move"
				$sDispType = GetLangText("ActionMove")
			Case "left_down"
				$sDispType = GetLangText("ActionLBtnDown")
			Case "left_up"
				$sDispType = GetLangText("ActionLBtnUp")
			Case "right_down"
				$sDispType = GetLangText("ActionRBtnDown")
			Case "right_up"
				$sDispType = GetLangText("ActionRBtnUp")
			Case "key_down"
				$sDispType = GetLangText("ActionKeyDown")
			Case "key_up"
				$sDispType = GetLangText("ActionKeyUp")
			Case "delay"
				$sDispType = GetLangText("ActionDelay")
			Case Else
				$sDispType = $aActions[$n][1]
		EndSwitch

		Local $sLabelDisp = _ToDisplayLabel($aActions[$n][1], $aActions[$n][4])

		GUICtrlCreateListViewItem( _
				Round($aActions[$n][0], 0) & "|" & _
				$sDispType & "|" & _
				$aActions[$n][2] & "|" & _
				$aActions[$n][3] & "|" & _
				$sLabelDisp, _
				$List _
				)
	Next

	Local $cnt = UBound($aActions)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusLoaded") & $cnt & GetLangText("StatusStopRecord2"), 0)
	_GUICtrlStatusBar_SetText($hStatus, GetLangText("StatusActionCount") & $cnt, 1)
EndFunc   ;==>LoadMacro
posted @ 2026-03-10 14:14  ken-yu  阅读(795)  评论(0)    收藏  举报