总结
这个Python程序是一个基于Tkinter的GUI应用程序,用于录制和回放用户的鼠标和键盘操作。主要功能包括:
-
录制功能:
- 用户可以选择录制哪些类型的动作(如单击、双击、拖动、滚动、按键、移动)。
- 通过按
F1
键可以开始或停止录制。 - 录制的动作会显示在一个列表框中。
-
回放功能:
- 用户可以通过输入重复次数、延迟时间和间隔时间来配置回放参数。
- 通过按
F2
键可以开始回放录制的动作。 - 通过按
F3
键可以停止正在进行的回放。 - 回放的状态会在状态面板中显示。
-
保存和加载功能:
- 用户可以将录制的动作保存到文件中,默认文件名为"录制自动点击脚本.txt"。
- 用户可以从文件中加载之前保存的动作。
- 通过按
F4
键可以保存录制的动作。 - 通过按
F5
键可以加载录制的动作。
-
其他功能:
- 用户可以通过按
F6
键启动循环点击模式。 - 用户可以通过按
F7
键清除当前所有的录制动作。
- 用户可以通过按
主要组件
- ActionManager: 负责管理录制的动作,包括添加、获取、清除动作以及保存和加载动作到文件。
- RecordingController: 控制录制过程,监听鼠标的移动、点击、滚动事件以及键盘的按键事件,并记录相应的动作。
- Controller: 控制回放过程,负责执行录制的动作并处理相关的状态更新。
- RecordingPlaybackTab: 构建GUI界面,包含录制、回放、保存、加载等功能的控件,并绑定相应的事件处理函数。
这个程序适合需要自动化测试、演示或其他需要模拟用户操作的场景。
python
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from pynput import mouse, keyboard
from pynput.mouse import Listener as MouseListener
from pynput.keyboard import Listener as KeyboardListener
from threading import Thread
import time
import random # 导入 random 模块
class ActionManager:
def __init__(self):
self.actions = []
def add_action(self, action):
self.actions.append(action)
def get_actions(self):
return self.actions
def clear_actions(self):
self.actions.clear()
def save_to_file(self, file_path):
with open(file_path, 'w') as file:
for action in self.actions:
file.write(str(action) + '\n')
def load_from_file(self, file_path):
self.actions.clear()
with open(file_path, 'r') as file:
for line in file:
action = eval(line.strip())
self.actions.append(action)
class RecordingController:
def __init__(self, action_manager, update_action_listbox, cycle_clicks, toggle_recording,
record_single_click, record_double_click, record_dragging, record_scroll, record_key_press, record_mouse_move):
self.action_manager = action_manager
self.update_action_listbox = update_action_listbox
self.cycle_clicks = cycle_clicks
self.toggle_recording = toggle_recording
self.recording = False
self.mouse_listener = None
self.keyboard_listener = None
self.mouse = mouse.Controller()
self.keyboard = keyboard.Controller()
self.drag_start_pos = None
self.dragging = False
self.record_single_click = record_single_click
self.record_double_click = record_double_click
self.record_dragging = record_dragging
self.record_scroll = record_scroll
self.record_key_press = record_key_press
self.record_mouse_move = record_mouse_move
def on_move(self, x, y):
if self.recording and self.dragging and self.record_dragging.get():
action = {"type": "mouse_move", "position": (x, y)}
self.action_manager.add_action(action)
self.update_action_listbox(action)
elif self.recording and self.record_mouse_move.get():
action = {"type": "mouse_move", "position": (x, y)}
self.action_manager.add_action(action)
self.update_action_listbox(action)
def on_click(self, x, y, button, pressed):
if self.recording:
if not pressed and self.dragging and self.record_dragging.get():
self.dragging = False
action = {"type": "mouse_release", "position": (x, y), "button": str(button)}
self.action_manager.add_action(action)
self.update_action_listbox(action)
elif pressed and not self.dragging and self.record_dragging.get():
self.dragging = True
self.drag_start_pos = (x, y)
action = {"type": "mouse_press", "position": (x, y), "button": str(button)}
self.action_manager.add_action(action)
self.update_action_listbox(action)
else:
action = {"type": "mouse_click", "position": (x, y), "button": str(button), "pressed": pressed}
if self.record_single_click.get() or (not pressed and self.record_double_click.get()):
self.action_manager.add_action(action)
self.update_action_listbox(action)
def on_scroll(self, x, y, dx, dy):
if self.recording and self.record_scroll.get():
action = {"type": "mouse_scroll", "position": (x, y), "dx": dx, "dy": dy}
self.action_manager.add_action(action)
self.update_action_listbox(action)
def on_press(self, key):
try:
k = key.char
except AttributeError:
k = f"{key}"
if self.recording and self.record_key_press.get():
action = {"type": "key_press", "key": k}
self.action_manager.add_action(action)
self.update_action_listbox(action)
if key == keyboard.Key.f1:
self.toggle_recording()
elif key == keyboard.Key.f2:
self.start_playing()
elif key == keyboard.Key.f3:
self.stop_playing()
elif key == keyboard.Key.f4:
self.save_recorded_actions()
elif key == keyboard.Key.f5:
self.load_recorded_actions()
elif key == keyboard.Key.f6:
self.cycle_clicks()
elif key == keyboard.Key.f7:
self.clear_recorded_actions()
def start_recording(self):
if self.recording:
return
self.recording = True
self.mouse_listener = MouseListener(on_move=self.on_move, on_click=self.on_click, on_scroll=self.on_scroll)
self.keyboard_listener = KeyboardListener(on_press=self.on_press)
self.mouse_listener.start()
self.keyboard_listener.start()
def stop_recording(self):
if not self.recording:
return
self.recording = False
self.mouse_listener.stop()
self.keyboard_listener.stop()
class Controller:
def __init__(self, action_manager, recording_controller, update_action_listbox, update_status_panel):
self.action_manager = action_manager
self.recording_controller = recording_controller
self.update_action_listbox = update_action_listbox
self.update_status_panel = update_status_panel
self.playing = False
self.thread = None
def start_playing(self, repeat_times, min_interval, max_interval, delay):
if self.playing:
return
self.playing = True
self.thread = Thread(target=self._play_actions, args=(repeat_times, min_interval, max_interval, delay))
self.thread.start()
def _play_actions(self, repeat_times, min_interval, max_interval, delay):
actions = self.action_manager.get_actions()
if not actions:
self.update_status_panel("没有可用的动作进行播放。")
self.playing = False
return
time.sleep(delay / 1000) # Delay before starting playback
for _ in range(repeat_times):
if not self.playing:
break
for action in actions:
if not self.playing:
break
interval = random.uniform(min_interval, max_interval) # Generate a random interval within the specified range
if action["type"] == "mouse_click":
button = getattr(mouse.Button, action["button"].split('.')[1]) if '.' in action["button"] else mouse.Button.left
self.recording_controller.mouse.position = action["position"]
if action["pressed"]:
self.recording_controller.mouse.press(button)
else:
self.recording_controller.mouse.release(button)
elif action["type"] == "mouse_scroll":
self.recording_controller.mouse.position = action["position"]
self.recording_controller.mouse.scroll(action["dx"], action["dy"])
elif action["type"] == "key_press":
key = action["key"]
if key.startswith('Key'):
key = getattr(keyboard.Key, key.split('.')[1])
self.recording_controller.keyboard.press(key)
self.recording_controller.keyboard.release(key)
elif action["type"] == "mouse_move":
self.recording_controller.mouse.position = action["position"]
elif action["type"] == "mouse_press":
button = getattr(mouse.Button, action["button"].split('.')[1]) if '.' in action["button"] else mouse.Button.left
self.recording_controller.mouse.position = action["position"]
self.recording_controller.mouse.press(button)
elif action["type"] == "mouse_release":
button = getattr(mouse.Button, action["button"].split('.')[1]) if '.' in action["button"] else mouse.Button.left
self.recording_controller.mouse.position = action["position"]
self.recording_controller.mouse.release(button)
time.sleep(interval)
self.playing = False
self.update_status_panel("回放已完成")
def stop_playing(self):
self.playing = False
if self.thread and self.thread.is_alive():
self.thread.join()
class RecordingPlaybackTab(ttk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.repeat_times = 1 # 默认重复次数1次
self.delay = 0 # 默认延迟为0ms
self.min_interval = 0.01 # 默认最小间隔时间为0.1秒
self.max_interval = 0.1 # 默认最大间隔时间为1秒
self.action_manager = ActionManager()
self.record_single_click = tk.BooleanVar(value=False)
self.record_double_click = tk.BooleanVar(value=False)
self.record_dragging = tk.BooleanVar(value=False)
self.record_scroll = tk.BooleanVar(value=False)
self.record_key_press = tk.BooleanVar(value=False)
self.record_mouse_move = tk.BooleanVar(value=False)
self.recording_controller = RecordingController(
self.action_manager,
self.update_action_listbox,
self.cycle_clicks,
self.toggle_recording,
self.record_single_click,
self.record_double_click,
self.record_dragging,
self.record_scroll,
self.record_key_press,
self.record_mouse_move
)
self.controller = Controller(self.action_manager, self.recording_controller, self.update_action_listbox, self.update_status_panel)
self.create_widgets()
def create_widgets(self):
# 设置主框架
main_frame = ttk.Frame(self)
main_frame.pack(fill=tk.BOTH, expand=True)
# 录制和回放控制区
control_frame = ttk.Frame(main_frame)
control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10)
# 重复次数
repeat_times_frame = ttk.Frame(control_frame)
repeat_times_label = ttk.Label(repeat_times_frame, text="重复次数:")
self.repeat_times_entry = ttk.Entry(repeat_times_frame, width=5)
self.repeat_times_entry.insert(0, str(self.repeat_times)) # 默认1次
repeat_times_label.grid(row=0, column=0, padx=(10, 0), pady=(10, 0))
self.repeat_times_entry.grid(row=0, column=1, padx=(0, 10), pady=(10, 0))
repeat_times_frame.pack(padx=10, fill=tk.X)
# 延迟
delay_frame = ttk.Frame(control_frame)
delay_label = ttk.Label(delay_frame, text="延迟(ms):")
self.delay_entry = ttk.Entry(delay_frame, width=5)
self.delay_entry.insert(0, str(self.delay)) # 默认0ms
delay_label.grid(row=0, column=0, padx=(10, 0), pady=(10, 0))
self.delay_entry.grid(row=0, column=1, padx=(0, 10), pady=(10, 0))
delay_frame.pack(padx=10, fill=tk.X)
# 最小间隔时间
min_interval_frame = ttk.Frame(control_frame)
min_interval_label = ttk.Label(min_interval_frame, text="最小间隔时间(秒):")
self.min_interval_entry = ttk.Entry(min_interval_frame, width=5)
self.min_interval_entry.insert(0, str(self.min_interval)) # 默认值0.1秒
min_interval_label.grid(row=0, column=0, padx=(10, 0), pady=(10, 0))
self.min_interval_entry.grid(row=0, column=1, padx=(0, 10), pady=(10, 0))
min_interval_frame.pack(padx=10, fill=tk.X)
# 最大间隔时间
max_interval_frame = ttk.Frame(control_frame)
max_interval_label = ttk.Label(max_interval_frame, text="最大间隔时间(秒):")
self.max_interval_entry = ttk.Entry(max_interval_frame, width=5)
self.max_interval_entry.insert(0, str(self.max_interval)) # 默认值1秒
max_interval_label.grid(row=0, column=0, padx=(10, 0), pady=(10, 0))
self.max_interval_entry.grid(row=0, column=1, padx=(0, 10), pady=(10, 0))
max_interval_frame.pack(padx=10, fill=tk.X)
# 录制选项
record_options_frame = ttk.LabelFrame(control_frame, text="录制选项")
record_options_frame.pack(padx=10, pady=10, fill=tk.X)
single_click_checkbox = ttk.Checkbutton(record_options_frame, text="单击", variable=self.record_single_click)
double_click_checkbox = ttk.Checkbutton(record_options_frame, text="双击", variable=self.record_double_click)
dragging_checkbox = ttk.Checkbutton(record_options_frame, text="拖动", variable=self.record_dragging)
scroll_checkbox = ttk.Checkbutton(record_options_frame, text="滚动", variable=self.record_scroll)
key_press_checkbox = ttk.Checkbutton(record_options_frame, text="按键", variable=self.record_key_press)
mouse_move_checkbox = ttk.Checkbutton(record_options_frame, text="移动", variable=self.record_mouse_move)
single_click_checkbox.grid(row=0, column=0, sticky=tk.W, padx=10, pady=5)
double_click_checkbox.grid(row=1, column=0, sticky=tk.W, padx=10, pady=5)
dragging_checkbox.grid(row=2, column=0, sticky=tk.W, padx=10, pady=5)
scroll_checkbox.grid(row=0, column=1, sticky=tk.W, padx=10, pady=5)
key_press_checkbox.grid(row=1, column=1, sticky=tk.W, padx=10, pady=5)
mouse_move_checkbox.grid(row=2, column=1, sticky=tk.W, padx=10, pady=5)
# 录制按钮
record_button = ttk.Button(
control_frame, text="开始/停止录制 (F7)", command=self.toggle_recording
)
record_button.pack(pady=(10, 0))
# 清除录制按钮
clear_button = ttk.Button(
control_frame, text="清除录制 (F1)", command=self.clear_recorded_actions
)
clear_button.pack(pady=(10, 0))
# 回放按钮
play_button = ttk.Button(
control_frame, text="开始回放 (F2)", command=self.start_playing
)
play_button.pack(pady=(10, 0))
# 停止回放按钮
self.stop_button = ttk.Button(
control_frame, text="停止回放 (F3)", command=self.stop_playing, state=tk.DISABLED
)
self.stop_button.pack(pady=(10, 0))
# 保存录制的动作到文件
save_button = ttk.Button(
control_frame, text="保存录制的动作 (F4)", command=self.save_recorded_actions
)
save_button.pack(pady=(10, 0))
# 加载录制的动作从文件
load_button = ttk.Button(
control_frame, text="加载录制的动作 (F5)", command=self.load_recorded_actions
)
load_button.pack(pady=(10, 0))
# 记录的动作列表
self.action_listbox = tk.Listbox(main_frame, height=30, width=100)
self.action_listbox.pack(pady=(10, 0), fill=tk.BOTH, expand=True)
# 状态面板
status_frame = ttk.Frame(main_frame)
status_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=10)
self.status_text = tk.Text(status_frame, wrap=tk.WORD, height=10, width=100)
self.status_text.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)
def toggle_recording(self):
if self.recording_controller.recording:
self.recording_controller.stop_recording()
self.update_action_listbox()
self.update_status_panel("录制已停止")
else:
self.recording_controller.start_recording()
self.update_status_panel("正在录制... 按 F1 结束录制")
def update_action_listbox(self, action=None):
self.action_listbox.delete(0, tk.END)
for action in self.action_manager.get_actions():
formatted_action = self.format_action(action)
self.action_listbox.insert(tk.END, formatted_action)
def format_action(self, action):
if action["type"] == "mouse_click":
press_state = "按下" if action["pressed"] else "释放"
return f"鼠标点击 ({action['position'][0]}, {action['position'][1]}), 按钮: {action['button']}, 状态: {press_state}"
elif action["type"] == "mouse_scroll":
return f"鼠标滚轮 ({action['position'][0]}, {action['position'][1]}), 变量: ({action['dx']}, {action['dy']})"
elif action["type"] == "key_press":
return f"按键: {action['key']}"
elif action["type"] == "mouse_move":
return f"鼠标移动到 ({action['position'][0]}, {action['position'][1]})"
elif action["type"] == "mouse_press":
return f"鼠标按住 ({action['position'][0]}, {action['position'][1]}), 按钮: {action['button']}"
elif action["type"] == "mouse_release":
return f"鼠标释放 ({action['position'][0]}, {action['position'][1]}), 按钮: {action['button']}"
return ""
def validate_interval(self, value_if_allowed):
try:
if float(value_if_allowed) <= 0 or float(value_if_allowed) > 10:
raise ValueError
return True
except ValueError:
return False
def start_playing(self):
try:
repeat_times = int(self.repeat_times_entry.get())
delay = int(self.delay_entry.get())
min_interval = float(self.min_interval_entry.get())
max_interval = float(self.max_interval_entry.get())
except ValueError:
self.update_status_panel("请输入有效的数值。")
return
if min_interval < 0 or min_interval > 60 or max_interval < 0 or max_interval > 60 or min_interval >= max_interval:
messagebox.showwarning("无效的间隔时间", "请将最小和最大间隔时间设置在0.1秒到10秒之间,并且最小间隔时间应小于最大间隔时间。")
return
self.controller.start_playing(repeat_times, min_interval, max_interval, delay)
self.stop_button["state"] = tk.NORMAL
def stop_playing(self):
self.controller.stop_playing()
self.stop_button["state"] = tk.DISABLED
def save_recorded_actions(self):
default_filename = "录制自动点击脚本.txt"
file_path = filedialog.asksaveasfilename(defaultextension=".txt",
initialfile=default_filename,
filetypes=[("文本文件", "*.txt"),
("所有文件", "*.*")])
if file_path:
self.action_manager.save_to_file(file_path)
self.update_status_panel(f"动作已保存到 {file_path}")
def load_recorded_actions(self):
file_path = filedialog.askopenfilename(filetypes=[("文本文件", "*.txt"),
("所有文件", "*.*")])
if file_path:
self.action_manager.load_from_file(file_path)
self.update_action_listbox()
self.update_status_panel(f"动作已从 {file_path} 加载")
def cycle_clicks(self):
if self.action_manager.get_actions():
self.controller.start_playing(999, 0, 0, 0) # 循环点击,无间隔,无延迟
self.update_status_panel("循环点击启动")
def clear_recorded_actions(self):
self.action_manager.clear_actions()
self.update_action_listbox()
self.update_status_panel("录制已清除")
def update_status_panel(self, message):
self.status_text.insert(tk.END, message + "\n")
self.status_text.see(tk.END)
if __name__ == "__main__":
root = tk.Tk()
root.title("动作录制与回放工具")
tab = RecordingPlaybackTab(root)
tab.pack(fill=tk.BOTH, expand=True)
# 监听全局键盘事件
global_keyboard_listener = KeyboardListener(on_press=lambda key: tab.recording_controller.on_press(key))
global_keyboard_listener.start()
root.mainloop()