吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2426|回复: 76
上一主题 下一主题
收起左侧

[原创工具] 宏录制器

  [复制链接]
跳转到指定楼层
楼主
gzsklsskszngc 发表于 2024-12-29 15:37 回帖奖励
之前写了一个图像识别与点击工具,心血来潮又写了宏录制器。
感觉这个无脑的工具应该在某些情况下也挺实用。
照例先上代码
[Python] 纯文本查看 复制代码
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
import time
from pynput import mouse, keyboard
from enum import Enum
import json
import pystray
from PIL import Image
import datetime

class EventType(Enum):
    """定义事件类型枚举"""
    KEY_PRESS = 1
    KEY_RELEASE = 2
    MOUSE_MOVE = 3
    MOUSE_CLICK = 4
    MOUSE_SCROLL = 5
    MOUSE_DOUBLE_CLICK = 6

class Event:
    """定义事件类,用于存储事件信息"""
    def __init__(self, type, timestamp, **kwargs):
        self.type = type
        self.timestamp = timestamp
        self.data = kwargs

    def to_dict(self):
      """转换为字典,方便json序列化"""
      return {
          "type": self.type.value,
          "timestamp": self.timestamp,
          "data": self.data
      }
    @classmethod
    def from_dict(cls,data):
       """从字典创建事件实例"""
       return cls(EventType(data["type"]),data["timestamp"],**data["data"])

    def __str__(self):
       if self.type == EventType.KEY_PRESS or self.type == EventType.KEY_RELEASE:
          return f"{self.type.name}: {self.data.get('key')}, 时间:{self.timestamp:.2f}"
       elif self.type == EventType.MOUSE_MOVE:
          return f"{self.type.name}: X={self.data.get('x')}, Y={self.data.get('y')}, 时间:{self.timestamp:.2f}"
       elif self.type == EventType.MOUSE_CLICK:
            return f"{self.type.name}: {self.data.get('button')}, X={self.data.get('x')}, Y={self.data.get('y')}, 时间:{self.timestamp:.2f}"
       elif self.type == EventType.MOUSE_SCROLL:
           return f"{self.type.name}: X={self.data.get('dx')}, Y={self.data.get('dy')}, 时间:{self.timestamp:.2f}"
       else:
          return "Unknown event"

class MacroRecorderApp:
    def __init__(self, root):
        self.root = root
        self.root.title("宏录制器")
        self.root.geometry("800x600")
        # 最小化时隐藏主窗口
        self.root.protocol('WM_DELETE_WINDOW', self.withdraw_window)

        self.is_recording = False
        self.is_paused = False # 暂停状态
        self.recorded_events = []
        self.start_time = None
        self.pause_time = None  # 记录暂停开始的时间
        self.total_pause_duration = 0 # 记录总的暂停时长
        self.mouse_listener = None
        self.keyboard_listener = None
        self.mouse_controller = mouse.Controller()
        self.keyboard_controller = keyboard.Controller()
        self.hotkey_listener = None
        self.tray_icon = None
        self.loop_count = tk.IntVar(value=1) # 循环次数
        self.infinite_loop = tk.BooleanVar(value=False) # 是否无限循环
        self.stop_playback = False # 停止播放的标志
        self.scheduled_playback = False # 定时播放状态
        self.scheduled_time = tk.StringVar(value="")  # 定时播放时间

        # 默认热键
        self.record_hotkey = '<ctrl>+<alt>+r'   # 录制热键
        self.stop_record_hotkey = '<ctrl>+<alt>+s'  # 停止录制热键
        self.play_hotkey = '<ctrl>+<alt>+p'  # 播放热键
        self.pause_resume_hotkey = '<ctrl>+<alt>+a'  # 暂停/恢复热键
        self.stop_playback_hotkey = '<ctrl>+<alt>+x'  # 停止播放热键

        # 事件类型选择
        self.record_mouse_move = tk.BooleanVar(value=True)  # 记录鼠标移动事件
        self.record_mouse_click = tk.BooleanVar(value=True)  # 记录鼠标点击事件
        self.record_mouse_scroll = tk.BooleanVar(value=True)    # 记录鼠标滚轮事件
        self.record_key_press = tk.BooleanVar(value=True)    # 记录键盘按下事件
        self.record_key_release = tk.BooleanVar(value=True)  # 记录键盘释放事件

        self._create_widgets()
        self._apply_theme("clam")
        self._setup_hotkeys()
        self._register_hotkey_listener()
        self._setup_tray_icon()

    def _apply_theme(self,theme_name):
       """应用主题"""
       style=ttk.Style(self.root)
       style.theme_use(theme_name)
       # 统一字体
       style.configure(".", font=("Arial", 10))
       # 设置按钮样式
       style.configure("TButton", padding=5,relief="raised")
       style.map("TButton", foreground=[('active', 'black')], background=[('active', 'lightgray')])

    def _create_widgets(self):
        # 顶部框架
        top_frame = ttk.Frame(self.root, padding=10)
        top_frame.pack(fill=tk.X)

        # 录制按钮
        self.record_button = ttk.Button(top_frame, text="录制", command=self.toggle_record)
        self.record_button.pack(side=tk.LEFT, padx=5)

        # 暂停/恢复按钮
        self.pause_button = ttk.Button(top_frame, text="暂停", command=self.toggle_pause_resume, state=tk.DISABLED)
        self.pause_button.pack(side=tk.LEFT, padx=5)

        # 播放按钮
        self.play_button = ttk.Button(top_frame, text="播放", command=self.play_macro, state=tk.DISABLED)
        self.play_button.pack(side=tk.LEFT, padx=5)

        # 保存按钮
        self.save_button = ttk.Button(top_frame, text="保存", command=self.save_macro, state=tk.DISABLED)
        self.save_button.pack(side=tk.LEFT, padx=5)

        # 加载按钮
        self.load_button = ttk.Button(top_frame, text="加载", command=self.load_macro)
        self.load_button.pack(side=tk.LEFT, padx=5)

        # 删除按钮
        self.delete_button = ttk.Button(top_frame, text="删除", command=self._delete_event, state=tk.DISABLED)
        self.delete_button.pack(side=tk.LEFT, padx=5)

        # 热键设置按钮
        self.hotkey_button = ttk.Button(top_frame, text="设置热键", command=self._open_hotkey_settings)
        self.hotkey_button.pack(side=tk.LEFT, padx=5)

        # 循环和定时播放设置框架 (合并在同一行)
        settings_frame = ttk.Frame(self.root, padding=10)
        settings_frame.pack(fill=tk.X)

        # 循环设置框架
        loop_frame = ttk.LabelFrame(settings_frame, text="循环设置", padding=10)
        loop_frame.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        # 循环次数
        ttk.Label(loop_frame, text="循环次数:").pack(side=tk.LEFT, padx=5)
        loop_count_entry = ttk.Entry(loop_frame, textvariable=self.loop_count, width=5)
        loop_count_entry.pack(side=tk.LEFT, padx=5)

        # 无限循环
        ttk.Checkbutton(loop_frame, text="无限循环", variable=self.infinite_loop).pack(side=tk.LEFT, padx=5)

        # 定时播放框架
        schedule_frame = ttk.LabelFrame(settings_frame, text="定时播放", padding=10)
        schedule_frame.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        # 定时播放时间标签和输入框
        ttk.Label(schedule_frame, text="定时时间(HH:MM:SS):").pack(side=tk.LEFT, padx=5)
        self.schedule_entry = ttk.Entry(schedule_frame, textvariable=self.scheduled_time, width=10)
        self.schedule_entry.pack(side=tk.LEFT, padx=5)
        # 定时播放按钮
        self.schedule_button = ttk.Button(schedule_frame, text="设置定时播放", command=self._schedule_playback, state=tk.DISABLED)
        self.schedule_button.pack(side=tk.LEFT, padx=5)

        # 事件类型选择框架
        event_type_frame = ttk.LabelFrame(self.root, text="选择录制事件类型", padding=10)
        event_type_frame.pack(fill=tk.X, padx=10, pady=5)

        # 鼠标事件复选框
        ttk.Checkbutton(event_type_frame, text="鼠标移动", variable=self.record_mouse_move).pack(side=tk.LEFT, padx=5)
        ttk.Checkbutton(event_type_frame, text="鼠标点击", variable=self.record_mouse_click).pack(side=tk.LEFT, padx=5)
        ttk.Checkbutton(event_type_frame, text="鼠标滚轮", variable=self.record_mouse_scroll).pack(side=tk.LEFT, padx=5)

        # 键盘事件复选框
        ttk.Checkbutton(event_type_frame, text="键盘按下", variable=self.record_key_press).pack(side=tk.LEFT, padx=5)
        ttk.Checkbutton(event_type_frame, text="键盘释放", variable=self.record_key_release).pack(side=tk.LEFT, padx=5)

        # 事件列表框架
        list_frame = ttk.Frame(self.root, padding=10)
        list_frame.pack(fill=tk.BOTH, expand=True)

        # 事件列表
        self.event_list = tk.Listbox(list_frame, selectmode=tk.SINGLE)
        self.event_list.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.event_list.bind("<<ListboxSelect>>", self._enable_delete_button)
        self.event_list.bind("<Double-Button-1>",self._edit_event)

        # 垂直滚动条
        scrollbar=ttk.Scrollbar(list_frame,orient=tk.VERTICAL,command=self.event_list.yview)
        scrollbar.pack(side=tk.RIGHT,fill=tk.Y)
        self.event_list.config(yscrollcommand=scrollbar.set)

    def _setup_hotkeys(self):
        """设置热键组合"""
        self.record_hotkey_combination = keyboard.HotKey.parse(self.record_hotkey)
        self.stop_record_hotkey_combination = keyboard.HotKey.parse(self.stop_record_hotkey)    
        self.play_hotkey_combination = keyboard.HotKey.parse(self.play_hotkey)
        self.pause_resume_hotkey_combination = keyboard.HotKey.parse(self.pause_resume_hotkey)
        self.stop_playback_hotkey_combination = keyboard.HotKey.parse(self.stop_playback_hotkey)

    def _register_hotkey_listener(self):
        """注册热键监听器"""
        self.hotkey_listener = keyboard.GlobalHotKeys({
            self.record_hotkey: self._start_recording,  # 录制热键
            self.stop_record_hotkey: self._stop_recording,  # 停止录制热键
            self.play_hotkey: self.play_macro,  # 播放热键
            self.pause_resume_hotkey: self.toggle_pause_resume,  # 暂停/恢复热键
            self.stop_playback_hotkey: self._stop_playback  # 停止播放热键
        })
        self.hotkey_listener.start()

    def _stop_playback(self):
      """停止播放"""
      self.stop_playback = True

    def _open_hotkey_settings(self):
        """打开热键设置对话框"""
        settings_window = tk.Toplevel(self.root)
        settings_window.title("热键设置")
        settings_window.geometry("300x300") # 调整窗口大小

        # 录制热键
        record_label = ttk.Label(settings_window, text="录制热键:")
        record_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
        self.record_hotkey_entry = ttk.Entry(settings_window)
        self.record_hotkey_entry.insert(0, self.record_hotkey)
        self.record_hotkey_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")

        # 停止录制热键
        stop_record_label = ttk.Label(settings_window, text="停止录制热键:")
        stop_record_label.grid(row=1, column=0, padx=5, pady=5, sticky="w")
        self.stop_record_hotkey_entry = ttk.Entry(settings_window)
        self.stop_record_hotkey_entry.insert(0, self.stop_record_hotkey)
        self.stop_record_hotkey_entry.grid(row=1, column=1, padx=5, pady=5, sticky="ew")

        # 播放热键
        play_label = ttk.Label(settings_window, text="播放热键:")
        play_label.grid(row=2, column=0, padx=5, pady=5, sticky="w")
        self.play_hotkey_entry = ttk.Entry(settings_window)
        self.play_hotkey_entry.insert(0, self.play_hotkey)
        self.play_hotkey_entry.grid(row=2, column=1, padx=5, pady=5, sticky="ew")

        # 暂停/恢复热键
        pause_resume_label = ttk.Label(settings_window, text="暂停/恢复热键:")
        pause_resume_label.grid(row=3, column=0, padx=5, pady=5, sticky="w")
        self.pause_resume_hotkey_entry = ttk.Entry(settings_window)
        self.pause_resume_hotkey_entry.insert(0, self.pause_resume_hotkey)
        self.pause_resume_hotkey_entry.grid(row=3, column=1, padx=5, pady=5, sticky="ew")

        # 停止播放热键
        stop_playback_label = ttk.Label(settings_window, text="停止播放热键:")
        stop_playback_label.grid(row=4, column=0, padx=5, pady=5, sticky="w")
        self.stop_playback_hotkey_entry = ttk.Entry(settings_window)
        self.stop_playback_hotkey_entry.insert(0, self.stop_playback_hotkey)
        self.stop_playback_hotkey_entry.grid(row=4, column=1, padx=5, pady=5, sticky="ew")

        # 保存按钮
        save_button = ttk.Button(settings_window, text="保存", command=self._save_hotkey_settings)
        save_button.grid(row=5, column=0, columnspan=2, padx=5, pady=10)

    def _save_hotkey_settings(self):
        """保存热键设置"""
        new_record_hotkey = self.record_hotkey_entry.get()
        new_stop_record_hotkey = self.stop_record_hotkey_entry.get()
        new_play_hotkey = self.play_hotkey_entry.get()
        new_pause_resume_hotkey = self.pause_resume_hotkey_entry.get()
        new_stop_playback_hotkey = self.stop_playback_hotkey_entry.get()

        try:
            # 验证热键
            keyboard.HotKey.parse(new_record_hotkey)
            keyboard.HotKey.parse(new_stop_record_hotkey)
            keyboard.HotKey.parse(new_play_hotkey)
            keyboard.HotKey.parse(new_pause_resume_hotkey)
            keyboard.HotKey.parse(new_stop_playback_hotkey)

            # 更新热键
            self.record_hotkey = new_record_hotkey
            self.stop_record_hotkey = new_stop_record_hotkey
            self.play_hotkey = new_play_hotkey
            self.pause_resume_hotkey = new_pause_resume_hotkey
            self.stop_playback_hotkey = new_stop_playback_hotkey
            self._setup_hotkeys()

            # 停止并重新启动热键侦听器以应用更改
            if self.hotkey_listener:
              self.hotkey_listener.stop()
            self._register_hotkey_listener()

            messagebox.showinfo("成功", "热键设置已保存")
        except ValueError:
            messagebox.showerror("错误", "无效的热键组合")

    def toggle_record(self):
      """
      切换录制状态。
      为了兼容使用界面按钮触发录制,此函数做判断
      """
      if not self.is_recording:
            self._start_recording()
      else:
            self._stop_recording()

    def toggle_pause_resume(self):
        """切换暂停/恢复状态"""
        if not self.is_recording:
            return

        if self.is_paused:
            self._resume_recording()
        else:
            self._pause_recording()

    def _start_recording(self):
        """开始录制"""
        # 如果正在录制,则不执行任何操作
        if self.is_recording:
            return
        self.is_recording = True
        self.is_paused = False
        self.record_button.config(text="停止")
        self.pause_button.config(state=tk.NORMAL, text="暂停")
        self.play_button.config(state=tk.DISABLED)
        self.save_button.config(state=tk.DISABLED)
        self.delete_button.config(state=tk.DISABLED)
        self.schedule_button.config(state=tk.DISABLED) # 禁用定时播放按钮
        self.recorded_events = []
        self.event_list.delete(0,tk.END)  # 清空列表

        self.start_time = time.time()
        self.total_pause_duration = 0
        self.mouse_listener = mouse.Listener(
            on_move=self._on_mouse_move if self.record_mouse_move.get() else self._do_nothing,
            on_click=self._on_mouse_click if self.record_mouse_click.get() else self._do_nothing,
            on_scroll=self._on_mouse_scroll if self.record_mouse_scroll.get() else self._do_nothing
        )
        self.keyboard_listener = keyboard.Listener(
            on_press=self._on_key_press if self.record_key_press.get() else self._do_nothing,
            on_release=self._on_key_release if self.record_key_release.get() else self._do_nothing
        )
        self.mouse_listener.start()
        self.keyboard_listener.start()

    def _pause_recording(self):
        """暂停录制"""
        if not self.is_recording or self.is_paused:
            return

        self.is_paused = True
        self.pause_button.config(text="恢复")
        self.pause_time = time.time()

        # 通过修改回调函数为空函数来实现,无论是否选择事件类型都执行
        self.mouse_listener.on_move = self._do_nothing
        self.mouse_listener.on_click = self._do_nothing
        self.mouse_listener.on_scroll = self._do_nothing
        self.keyboard_listener.on_press = self._do_nothing
        self.keyboard_listener.on_release = self._do_nothing

    def _resume_recording(self):
        """恢复录制"""
        if not self.is_recording or not self.is_paused:
            return

        self.is_paused = False
        self.pause_button.config(text="暂停")
        # 更新总的暂停时间
        self.total_pause_duration += (time.time() - self.pause_time)

        # 恢复监听鼠标和键盘事件,根据选择的事件类型执行
        self.mouse_listener.on_move = self._on_mouse_move if self.record_mouse_move.get() else self._do_nothing
        self.mouse_listener.on_click = self._on_mouse_click if self.record_mouse_click.get() else self._do_nothing
        self.mouse_listener.on_scroll = self._on_mouse_scroll if self.record_mouse_scroll.get() else self._do_nothing
        self.keyboard_listener.on_press = self._on_key_press if self.record_key_press.get() else self._do_nothing
        self.keyboard_listener.on_release = self._on_key_release if self.record_key_release.get() else self._do_nothing

    def _do_nothing(self, *args):
      """空函数,用于在暂停时替换事件回调函数"""
      pass

    def _stop_recording(self):
        """停止录制"""
        # 如果没有录制,则不执行任何操作
        if not self.is_recording:
            return
        self.is_recording = False
        self.is_paused = False
        self.record_button.config(text="录制")
        self.pause_button.config(state=tk.DISABLED, text="暂停")
        self.play_button.config(state=tk.NORMAL)
        self.save_button.config(state=tk.NORMAL)
        self.schedule_button.config(state=tk.NORMAL)
        if self.mouse_listener:
          self.mouse_listener.stop()
        if self.keyboard_listener:
          self.keyboard_listener.stop()
        self.mouse_listener = None
        self.keyboard_listener = None

    def _on_mouse_move(self, x, y):
        """鼠标移动事件处理"""
        if not self.is_recording or self.is_paused or not self.record_mouse_move.get():
           return
        timestamp = time.time() - self.start_time - self.total_pause_duration
        event=Event(EventType.MOUSE_MOVE,timestamp,x=x,y=y)
        self.recorded_events.append(event)
        self.event_list.insert(tk.END,str(event))

    def _on_mouse_click(self, x, y, button, pressed):
        """鼠标点击事件处理"""
        if not self.is_recording or self.is_paused or not self.record_mouse_click.get():
            return
        timestamp = time.time() - self.start_time - self.total_pause_duration
        if pressed:
            event = Event(EventType.MOUSE_CLICK, timestamp, x=x, y=y, button=str(button))
            self.recorded_events.append(event)
            self.event_list.insert(tk.END, str(event))
        else:
            # 处理双击事件
            if button == mouse.Button.left:
                if self.event_list.size() > 0 and self.event_list.get(self.event_list.size() - 1).startswith("MOUSE_CLICK"):
                    last_event = self.recorded_events[-1]
                    if last_event.type == EventType.MOUSE_CLICK and last_event.data['button'] == str(button):
                        double_click_event = Event(EventType.MOUSE_DOUBLE_CLICK, timestamp, x=x, y=y, button=str(button))
                        self.recorded_events.append(double_click_event)
                        self.event_list.insert(tk.END, str(double_click_event))

    def _on_mouse_scroll(self, x, y, dx, dy):
        """鼠标滚轮事件处理"""
        if not self.is_recording or self.is_paused or not self.record_mouse_scroll.get():
           return
        timestamp = time.time() - self.start_time - self.total_pause_duration
        event= Event(EventType.MOUSE_SCROLL,timestamp,dx=dx,dy=dy)
        self.recorded_events.append(event)
        self.event_list.insert(tk.END,str(event))

    def _on_key_press(self, key):
        """键盘按键按下事件处理,增强版"""
        if not self.is_recording or self.is_paused or not self.record_key_press.get():
            return
        timestamp = time.time() - self.start_time - self.total_pause_duration
        try:
            key_char = key.char if key.char else str(key)  # 获取按键字符表示
        except AttributeError:
            key_char = str(key)

        # 处理左右 Ctrl、Shift、Alt 键
        if key in (keyboard.Key.ctrl_l, keyboard.Key.ctrl_r):
            key_char = "Left Ctrl" if key == keyboard.Key.ctrl_l else "Right Ctrl"
        elif key in (keyboard.Key.shift_l, keyboard.Key.shift_r):
            key_char = "Left Shift" if key == keyboard.Key.shift_l else "Right Shift"
        elif key in (keyboard.Key.alt_l, keyboard.Key.alt_r):
            key_char = "Left Alt" if key == keyboard.Key.alt_l else "Right Alt"

        event = Event(EventType.KEY_PRESS, timestamp, key=key_char)
        self.recorded_events.append(event)
        self.event_list.insert(tk.END, str(event))

    def _on_key_release(self, key):
        """键盘按键释放事件处理,增强版"""
        if not self.is_recording or self.is_paused or not self.record_key_release.get():
            return
        timestamp = time.time() - self.start_time - self.total_pause_duration
        try:
            key_char = key.char if key.char else str(key)  # 获取按键字符表示
        except AttributeError:
            key_char = str(key)

        # 处理左右 Ctrl、Shift、Alt 键
        if key in (keyboard.Key.ctrl_l, keyboard.Key.ctrl_r):
            key_char = "Left Ctrl" if key == keyboard.Key.ctrl_l else "Right Ctrl"
        elif key in (keyboard.Key.shift_l, keyboard.Key.shift_r):
            key_char = "Left Shift" if key == keyboard.Key.shift_l else "Right Shift"
        elif key in (keyboard.Key.alt_l, keyboard.Key.alt_r):
            key_char = "Left Alt" if key == keyboard.Key.alt_l else "Right Alt"

        event = Event(EventType.KEY_RELEASE, timestamp, key=key_char)
        self.recorded_events.append(event)
        self.event_list.insert(tk.END, str(event))

    def play_macro(self):
      """播放录制好的宏"""
      if not self.recorded_events:
         messagebox.showinfo("提示","没有录制任何宏")
         return
      self.play_button.config(state=tk.DISABLED)
      self.record_button.config(state=tk.DISABLED)
      self.pause_button.config(state=tk.DISABLED)
      self.save_button.config(state=tk.DISABLED)
      self.delete_button.config(state=tk.DISABLED)
      self.schedule_button.config(state=tk.DISABLED)
      self.stop_playback = False # 重置停止播放标志
      threading.Thread(target=self._playback_thread,daemon=True).start()

    def _playback_thread(self):
        """回放宏操作的后台线程"""
        print("Playback thread started")
        try:
            loop_counter = 0
            while True:
                if self.stop_playback:
                  break
                if not self.infinite_loop.get():
                    loop_counter += 1
                    if loop_counter > self.loop_count.get():
                        break
                
                print(f"Starting loop: {loop_counter if not self.infinite_loop.get() else 'Infinite'}")
                start_time = time.time()
                last_timestamp = 0
                for event in self.recorded_events:
                    if self.stop_playback:
                       break # 如果停止标志被设置,则退出循环
                    print(f"Event: {event}")
                    delay = event.timestamp - last_timestamp
                    if delay > 0:
                        print(f"Delaying {delay:.2f} seconds")
                        # 使用循环来检查停止标志
                        delay_start_time = time.time()
                        while time.time() - delay_start_time < delay:
                           if self.stop_playback:
                             break
                           time.sleep(0.01) # 每次循环等待0.01s

                    if event.type == EventType.KEY_PRESS:
                        key_str = event.data.get('key')
                        print(f"Pressing key: {key_str}")
                        try:
                            # 尝试作为普通字符按下
                            self.keyboard_controller.press(key_str)
                        except (ValueError, AttributeError):
                            # 处理特殊键
                            try:
                                # 尝试使用keyboard.Key找到特殊键
                                special_key = getattr(keyboard.Key, key_str.replace("Key.", ""))
                                self.keyboard_controller.press(special_key)
                            except AttributeError:
                                print(f"无法识别的键:{key_str}")

                    elif event.type == EventType.KEY_RELEASE:
                        key_str = event.data.get('key')
                        print(f"Releasing key: {key_str}")
                        try:
                            # 尝试作为普通字符释放
                            self.keyboard_controller.release(key_str)
                        except (ValueError, AttributeError):
                            # 处理特殊键
                            try:
                                # 尝试使用keyboard.Key找到特殊键
                                special_key = getattr(keyboard.Key, key_str.replace("Key.", ""))
                                self.keyboard_controller.release(special_key)
                            except AttributeError:
                                print(f"无法识别的键:{key_str}")
                    elif event.type == EventType.MOUSE_MOVE:
                        x = event.data.get('x')
                        y = event.data.get('y')
                        print(f"Moving mouse to: x={x}, y={y}")
                        self.mouse_controller.position = (x, y)
                    elif event.type == EventType.MOUSE_CLICK:
                        x = event.data.get('x')
                        y = event.data.get('y')
                        button = event.data.get('button').replace("Button.", "")
                        print(f"Clicking mouse at: x={x}, y={y}, button={button}")
                        self.mouse_controller.position = (x, y)
                        self.mouse_controller.click(mouse.Button[button], 1)
                    elif event.type == EventType.MOUSE_SCROLL:
                        dx = event.data.get('dx')
                        dy = event.data.get('dy')
                        print(f"Scrolling mouse by dx={dx}, dy={dy}")
                        self.mouse_controller.scroll(dx, dy)
                    last_timestamp = event.timestamp
                
        except Exception as e:
            print(f"Error during playback: {e}")
        finally:
            self.play_button.config(state=tk.NORMAL)
            self.record_button.config(state=tk.NORMAL)
            self.pause_button.config(state=tk.NORMAL)
            self.save_button.config(state=tk.NORMAL)
            self.delete_button.config(state=tk.NORMAL)
            self.schedule_button.config(state=tk.NORMAL)

    def save_macro(self):
      """保存宏到文件"""
      file_path = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("JSON Files", "*.json")])
      if file_path:
         try:
            with open(file_path, 'w') as f:
                json.dump([event.to_dict() for event in self.recorded_events], f, indent=4)
            messagebox.showinfo("成功", "宏已保存")
         except Exception as e:
            messagebox.showerror("错误", f"保存宏失败: {e}")

    def load_macro(self):
        """从文件加载宏"""
        file_path = filedialog.askopenfilename(defaultextension=".json", filetypes=[("JSON Files", "*.json")])
        if file_path:
           try:
             with open(file_path, 'r') as f:
                loaded_data = json.load(f)
                self.recorded_events = [Event.from_dict(data) for data in loaded_data]
                self.event_list.delete(0, tk.END)  # 清空列表框
                for event in self.recorded_events:
                    self.event_list.insert(tk.END,str(event))
                self.play_button.config(state=tk.NORMAL)
                self.save_button.config(state=tk.NORMAL)
                self.schedule_button.config(state=tk.NORMAL) # 启用定时播放按钮
                messagebox.showinfo("成功","宏已加载")
           except Exception as e:
             messagebox.showerror("错误", f"加载宏失败: {e}")

    def _stop_recording(self):
        """停止录制"""
        # 如果没有录制,则不执行任何操作
        if not self.is_recording:
            return
        self.is_recording = False
        self.is_paused = False
        self.record_button.config(text="录制")
        self.pause_button.config(state=tk.DISABLED, text="暂停")
        self.play_button.config(state=tk.NORMAL)
        self.save_button.config(state=tk.NORMAL)
        self.schedule_button.config(state=tk.NORMAL) # 启用定时播放按钮
        if self.mouse_listener:
          self.mouse_listener.stop()
        if self.keyboard_listener:
          self.keyboard_listener.stop()
        self.mouse_listener = None
        self.keyboard_listener = None
    
    def _delete_event(self):
        """删除选中的事件"""
        selection = self.event_list.curselection()
        if not selection:
            return
        index = selection[0]
        del self.recorded_events[index]
        self.event_list.delete(index)
        if not self.recorded_events:
            self.play_button.config(state=tk.DISABLED)
            self.save_button.config(state=tk.DISABLED)
            self.delete_button.config(state=tk.DISABLED)
            self.schedule_button.config(state=tk.DISABLED) # 禁用定时播放按钮
        else:
            # 确保如果列表框中还有项目,删除按钮保持启用状态
            self.delete_button.config(state=tk.NORMAL)

    def _edit_event(self, event = None):
       """编辑选中的事件"""
       selection= self.event_list.curselection()
       if not selection:
          return
       selected_index=selection[0]
       selected_event=self.recorded_events[selected_index]

       if selected_event.type == EventType.KEY_PRESS or selected_event.type == EventType.KEY_RELEASE:
         self._edit_key_event(selected_index,selected_event)
       elif selected_event.type == EventType.MOUSE_MOVE:
           self._edit_mouse_move_event(selected_index,selected_event)
       elif selected_event.type == EventType.MOUSE_CLICK:
         self._edit_mouse_click_event(selected_index,selected_event)
       elif selected_event.type == EventType.MOUSE_SCROLL:
           self._edit_mouse_scroll_event(selected_index,selected_event)
    
    def _edit_key_event(self,index,event):
       """编辑键盘事件"""
       edit_window= tk.Toplevel(self.root)
       edit_window.title("编辑键盘事件")

       key_label= ttk.Label(edit_window, text="按键:")
       key_label.grid(row=0,column=0,sticky="w",padx=5,pady=5)
       key_entry = ttk.Entry(edit_window)
       key_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
       key_entry.insert(0, event.data.get("key",""))

       timestamp_label = ttk.Label(edit_window, text="时间戳(秒):")
       timestamp_label.grid(row=1, column=0, sticky="w", padx=5, pady=5)
       timestamp_entry = ttk.Entry(edit_window)
       timestamp_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
       timestamp_entry.insert(0, f"{event.timestamp:.2f}")

       def save_edit():
          new_key= key_entry.get()
          event.data["key"]=new_key
          try:
              new_timestamp = float(timestamp_entry.get())
              if new_timestamp < 0:
                  raise ValueError("时间戳不能为负数")
              event.timestamp = new_timestamp
              self._update_event_list(index, event)
              edit_window.destroy()
          except ValueError as e:
              messagebox.showerror("错误", f"无效的时间戳: {e}")
       
       save_button = ttk.Button(edit_window,text="保存",command=save_edit)
       save_button.grid(row=2,column=0,columnspan=2,pady=5)

    def _edit_mouse_move_event(self,index, event):
        """编辑鼠标移动事件"""
        edit_window= tk.Toplevel(self.root)
        edit_window.title("编辑鼠标移动事件")

        x_label = ttk.Label(edit_window,text="X:")
        x_label.grid(row=0,column=0,sticky="w",padx=5,pady=5)
        x_entry = ttk.Entry(edit_window)
        x_entry.grid(row=0, column=1, sticky="ew", padx=        5, pady=5)
        x_entry.insert(0,event.data.get("x",""))

        y_label = ttk.Label(edit_window,text="Y:")
        y_label.grid(row=1,column=0,sticky="w",padx=5,pady=5)
        y_entry = ttk.Entry(edit_window)
        y_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
        y_entry.insert(0,event.data.get("y",""))
        
        timestamp_label = ttk.Label(edit_window, text="时间戳(秒):")
        timestamp_label.grid(row=2, column=0, sticky="w", padx=5, pady=5)
        timestamp_entry = ttk.Entry(edit_window)
        timestamp_entry.grid(row=2, column=1, sticky="ew", padx=5, pady=5)
        timestamp_entry.insert(0, f"{event.timestamp:.2f}")

        def save_edit():
            try:
               new_x = float(x_entry.get())
               new_y= float(y_entry.get())
               event.data["x"] =new_x
               event.data["y"] = new_y
               new_timestamp = float(timestamp_entry.get())
               if new_timestamp < 0:
                   raise ValueError("时间戳不能为负数")
               event.timestamp = new_timestamp
               self._update_event_list(index,event)
               edit_window.destroy()
            except ValueError as e:
               messagebox.showerror("错误",f"无效的输入: {e}")

        save_button = ttk.Button(edit_window,text="保存",command=save_edit)
        save_button.grid(row=3,column=0,columnspan=2,pady=5)

    def _edit_mouse_click_event(self,index,event):
        """编辑鼠标点击事件"""
        edit_window= tk.Toplevel(self.root)
        edit_window.title("编辑鼠标点击事件")

        x_label = ttk.Label(edit_window,text="X:")
        x_label.grid(row=0,column=0,sticky="w",padx=5,pady=5)
        x_entry = ttk.Entry(edit_window)
        x_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        x_entry.insert(0,event.data.get("x",""))

        y_label = ttk.Label(edit_window,text="Y:")
        y_label.grid(row=1,column=0,sticky="w",padx=5,pady=5)
        y_entry = ttk.Entry(edit_window)
        y_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
        y_entry.insert(0,event.data.get("y",""))

        button_label =ttk.Label(edit_window,text="按钮:")
        button_label.grid(row=2,column=0,sticky="w",padx=5,pady=5)
        button_combobox = ttk.Combobox(edit_window,values=[str(b) for b in mouse.Button])
        button_combobox.grid(row=2,column=1,sticky="ew",padx=5,pady=5)
        button_combobox.set(str(event.data.get("button")).replace("Button.",""))

        timestamp_label = ttk.Label(edit_window, text="时间戳(秒):")
        timestamp_label.grid(row=3, column=0, sticky="w", padx=5, pady=5)
        timestamp_entry = ttk.Entry(edit_window)
        timestamp_entry.grid(row=3, column=1, sticky="ew", padx=5, pady=5)
        timestamp_entry.insert(0, f"{event.timestamp:.2f}")

        def save_edit():
            try:
                new_x= float(x_entry.get())
                new_y= float(y_entry.get())
                new_button= button_combobox.get()
                event.data["x"]=new_x
                event.data["y"]=new_y
                event.data["button"]=new_button
                new_timestamp = float(timestamp_entry.get())
                if new_timestamp < 0:
                    raise ValueError("时间戳不能为负数")
                event.timestamp = new_timestamp
                self._update_event_list(index,event)
                edit_window.destroy()
            except ValueError as e:
                messagebox.showerror("错误",f"无效的输入: {e}")
        
        save_button = ttk.Button(edit_window,text="保存",command=save_edit)
        save_button.grid(row=4,column=0,columnspan=2,pady=5)

    def _edit_mouse_scroll_event(self,index,event):
       """编辑鼠标滚动事件"""
       edit_window= tk.Toplevel(self.root)
       edit_window.title("编辑鼠标滚动事件")

       dx_label=ttk.Label(edit_window,text="DX:")
       dx_label.grid(row=0,column=0,sticky="w",padx=5,pady=5)
       dx_entry=ttk.Entry(edit_window)
       dx_entry.grid(row=0,column=1,sticky="ew",padx=5,pady=5)
       dx_entry.insert(0,event.data.get("dx",""))
       
       dy_label=ttk.Label(edit_window,text="DY:")
       dy_label.grid(row=1,column=0,sticky="w",padx=5,pady=5)
       dy_entry=ttk.Entry(edit_window)
       dy_entry.grid(row=1,column=1,sticky="ew",padx=5,pady=5)
       dy_entry.insert(0,event.data.get("dy",""))

       timestamp_label = ttk.Label(edit_window, text="时间戳(秒):")
       timestamp_label.grid(row=2, column=0, sticky="w", padx=5, pady=5)
       timestamp_entry = ttk.Entry(edit_window)
       timestamp_entry.grid(row=2, column=1, sticky="ew", padx=5, pady=5)
       timestamp_entry.insert(0, f"{event.timestamp:.2f}")
       
       def save_edit():
           try:
               new_dx=float(dx_entry.get())
               new_dy=float(dy_entry.get())
               event.data["dx"]=new_dx
               event.data["dy"]=new_dy
               new_timestamp = float(timestamp_entry.get())
               if new_timestamp < 0:
                   raise ValueError("时间戳不能为负数")
               event.timestamp = new_timestamp
               self._update_event_list(index,event)
               edit_window.destroy()
           except ValueError as e:
               messagebox.showerror("错误",f"无效的输入: {e}")
       
       save_button=ttk.Button(edit_window,text="保存",command=save_edit)
       save_button.grid(row=3,column=0,columnspan=2,pady=5)

    def _update_event_list(self,index,event):
       """更新列表框"""
       self.recorded_events[index] =event
       self.event_list.delete(index)
       self.event_list.insert(index,str(event))

    def _delete_event(self):
        """删除选中的事件"""
        selection = self.event_list.curselection()
        if not selection:
            return
        index = selection[0]
        del self.recorded_events[index]
        self.event_list.delete(index)
        if not self.recorded_events:
            self.play_button.config(state=tk.DISABLED)
            self.save_button.config(state=tk.DISABLED)
            self.delete_button.config(state=tk.DISABLED)
            self.schedule_button.config(state=tk.DISABLED)
        else:
            # 确保如果列表框中还有项目,删除按钮保持启用状态
            self.delete_button.config(state=tk.NORMAL)

    def _enable_delete_button(self, _):
        """根据列表选中项是否选中启用/禁用删除按钮"""
        selection = self.event_list.curselection()
        if selection:
            self.delete_button.config(state=tk.NORMAL)
        else:
            self.delete_button.config(state=tk.DISABLED)

    def _schedule_playback(self):
        """设置定时播放"""
        if not self.recorded_events:
           messagebox.showinfo("提示","没有录制任何宏")
           return
        
        schedule_time_str = self.scheduled_time.get()
        try:
            schedule_time = datetime.datetime.strptime(schedule_time_str, "%H:%M:%S").time()
            now=datetime.datetime.now()
            schedule_datetime = datetime.datetime.combine(now,schedule_time)
            if schedule_datetime <= now:
                schedule_datetime += datetime.timedelta(days=1) # 如果设定时间小于等于当前时间,则设置为第二天
            delay_seconds = (schedule_datetime - now).total_seconds()
            self.scheduled_playback =True
            self.play_button.config(state=tk.DISABLED) # 禁用播放按钮
            self.record_button.config(state=tk.DISABLED)
            self.pause_button.config(state=tk.DISABLED)
            self.save_button.config(state=tk.DISABLED)
            self.delete_button.config(state=tk.DISABLED)
            self.schedule_button.config(text="取消定时",state=tk.DISABLED) # 禁用定时播放按钮
            self.schedule_button.config(command=self._cancel_scheduled_playback)
            messagebox.showinfo("提示",f"宏将在 {schedule_datetime.strftime('%Y-%m-%d %H:%M:%S')} 播放")
            threading.Timer(delay_seconds, self._scheduled_playback_trigger).start()
        except ValueError:
           messagebox.showerror("错误","定时时间格式错误,正确格式为HH:MM:SS")
    
    def _cancel_scheduled_playback(self):
        """取消定时播放"""
        self.scheduled_playback = False
        self.schedule_button.config(text="设置定时播放",state=tk.NORMAL) # 启用定时播放按钮
        self.schedule_button.config(command=self._schedule_playback)
        self.play_button.config(state=tk.NORMAL)
        self.record_button.config(state=tk.NORMAL)
        self.pause_button.config(state=tk.NORMAL)
        self.save_button.config(state=tk.NORMAL)
        self.delete_button.config(state=tk.NORMAL)
        messagebox.showinfo("提示","已取消定时播放")

    def _scheduled_playback_trigger(self):
        """定时播放触发"""
        if self.scheduled_playback:
          self.scheduled_playback = False # 保证只触发一次
          self.play_macro()
          # 还原定时播放按钮状态
          self.schedule_button.config(text="设置定时播放")
          self.schedule_button.config(command=self._schedule_playback)

    def _setup_tray_icon(self):
        """设置系统托盘图标"""
        image = Image.open("icon.ico")  # 替换为你的图标文件
        menu = (
            pystray.MenuItem('显示', self.show_window),
            pystray.MenuItem('录制', self.toggle_record),
            pystray.MenuItem('播放', self.play_macro),
            pystray.MenuItem('退出', self.quit_window)
        )
        self.tray_icon = pystray.Icon("Macro Recorder", image, "宏录制器", menu)
        self.tray_icon.run_detached()

    def withdraw_window(self):
        """隐藏主窗口"""
        self.root.withdraw()

    def show_window(self, icon, item):
        """显示主窗口"""
        self.root.deiconify()

    def quit_window(self, icon, item):
        """退出程序"""
        self.tray_icon.stop()
        self.root.quit()

if __name__ == "__main__":
    root = tk.Tk()
    app = MacroRecorderApp(root)
    root.mainloop()


双击可以编辑相应的鼠标或键盘事件
因为写了系统托盘,所以程序根目录要放一个自己喜欢的ico图标,不然无法启动哦!


https://wwts.lanzoub.com/iQ85W2jbow9i

免费评分

参与人数 24吾爱币 +27 热心值 +24 收起 理由
shenfeiqq + 1 + 1 谢谢@Thanks!
happyzss + 1 + 1 谢谢@Thanks!
xzf + 1 + 1 谢谢@Thanks!
a85401234 + 1 + 1 谢谢@Thanks!
bqi153 + 1 + 1 谢谢@Thanks!
zmllxh + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
风之暇想 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
chakins + 1 + 1 谢谢@Thanks!
guoruihotel + 1 + 1 谢谢@Thanks!
nizeze + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
ranqilailema + 1 + 1 我很赞同!
llzxb + 1 + 1 谢谢@Thanks!
抱薪风雪雾 + 1 + 1 谢谢@Thanks!
15235109295 + 1 + 1 谢谢@Thanks!
nb08611033 + 1 + 1 谢谢 @Thanks!
alexsanda + 1 + 1 谢谢@Thanks!
hamajiazu + 1 我很赞同!
wxn2023 + 1 + 1 用心讨论,共获提升!
yanglinman + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
kexue8 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
艾爱姆Joker + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Night1918 + 1 谢谢@Thanks!
macolma + 1 谢谢@Thanks!
Cmzlwc + 1 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
kexue8 发表于 2024-12-29 19:34
在程序所在的根目录下必须放一个icon.ico的图标,才能打开软件。
作者设计的非常独特,让小白使用者也有参与感
推荐
afrakim97 发表于 2024-12-29 16:39
还要放自己设置的ico图标,使用起来会很有参与感
推荐
xindian720 发表于 2024-12-29 17:23
如果录制完成后保持的文件能是exe的可执行文件就牛了
5#
10830 发表于 2024-12-29 16:20
可以识别屏幕出现的文本吗?
6#
ysjd22 发表于 2024-12-29 16:33
厉害啊。非常感谢
7#
Lew96 发表于 2024-12-29 16:40
看起来你非常不错 感谢分享
8#
hbu126 发表于 2024-12-29 16:43
高手,多谢分享
9#
tudouAny 发表于 2024-12-29 18:40
作者厉害&#128077;&#127995;支持一下
10#
ljf211 发表于 2024-12-29 19:37
非常感谢...
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-1-1 17:33

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表