From df05dbe6bd7c0ec05518d766ea07d012b0589b98 Mon Sep 17 00:00:00 2001 From: jack Date: Wed, 12 Nov 2025 15:59:27 +0800 Subject: [PATCH] first commit --- main.py | 216 ++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 21 ++++ target_img.png | Bin 0 -> 4258 bytes temp_mouse_control.py | 49 ++++++++++ 4 files changed, 286 insertions(+) create mode 100755 main.py create mode 100755 requirements.txt create mode 100644 target_img.png create mode 100755 temp_mouse_control.py diff --git a/main.py b/main.py new file mode 100755 index 0000000..6012ac7 --- /dev/null +++ b/main.py @@ -0,0 +1,216 @@ +import pyautogui +import cv2 +import numpy as np +import time +import threading +from pynput import keyboard +import os + +class MacImageClicker: + def __init__(self): + self.is_monitoring = False + self.target_image = None + self.confidence_threshold = 0.95 + self.hotkey = keyboard.Key.f8 + self.check_interval = 0.000001 + + # macOS 特定设置 + pyautogui.FAILSAFE = True + pyautogui.PAUSE = 0.1 + + # 查找目标图片 + self.target_image_path = self.find_target_image() + + def find_target_image(self): + """在项目根目录查找目标图片""" + current_dir = os.getcwd() + image_files = [] + + # 查找所有可能的图片文件 + for file in os.listdir(current_dir): + if file.lower().endswith(('.png', '.jpg', '.jpeg')): + image_files.append(file) + + if not image_files: + print("在项目根目录未找到任何图片文件 (png, jpg, jpeg)") + return None + + # 优先选择包含 target 关键字的图片 + target_files = [f for f in image_files if 'target' in f.lower()] + if target_files: + selected = target_files[0] + else: + selected = image_files[0] + + full_path = os.path.join(current_dir, selected) + print(f"找到目标图片: {selected}") + return full_path + + def load_target_image(self): + """加载目标图片""" + if not self.target_image_path: + print("未找到目标图片文件") + return False + + try: + self.target_image = cv2.imread(self.target_image_path) + if self.target_image is not None: + print(f"成功加载目标图片: {os.path.basename(self.target_image_path)}") + print(f"图片尺寸: {self.target_image.shape[1]}x{self.target_image.shape[0]}") + return True + else: + print("图片加载失败,可能是格式不支持") + return False + except Exception as e: + print(f"加载图片时出错: {e}") + return False + + def get_screenshot(self): + """获取屏幕截图 - macOS 版本""" + try: + # 使用 pyautogui 截图,在 macOS 上兼容性更好 + screenshot = pyautogui.screenshot() + screenshot_cv = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR) + return screenshot_cv + except Exception as e: + print(f"截图失败: {e}") + return None + + def find_and_click(self): + """在屏幕上查找目标图片并点击""" + if self.target_image is None: + return False + + try: + # 获取屏幕截图 + screenshot = self.get_screenshot() + if screenshot is None: + return False + + # 模板匹配 + result = cv2.matchTemplate(screenshot, self.target_image, cv2.TM_CCOEFF_NORMED) + min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) + + # 调试信息(可选,可以注释掉以减少输出) + # print(f"当前匹配度: {max_val:.3f}") + + if max_val >= self.confidence_threshold: + # 计算目标中心位置 + h, w = self.target_image.shape[:2] + center_x = max_loc[0] + w // 2 + center_y = max_loc[1] + h // 2 + + # 先记录当前位置 + current_pos = pyautogui.position() + print(f"📍 从当前位置 ({current_pos.x}, {current_pos.y}) 移动到目标位置 ({center_x}, {center_y})") + + pyautogui.moveTo(center_x, center_y, duration=0.0001) + + # 执行点击 + pyautogui.click() + + print(f"✅ 已点击位置: ({center_x}, {center_y}), 相似度: {max_val:.3f}") + return True + + except Exception as e: + print(f"查找或点击时出错: {e}") + + return False + + def monitoring_loop(self): + """监控循环""" + print("监控线程启动") + while True: + if self.is_monitoring: + self.find_and_click() + time.sleep(self.check_interval) + + def toggle_monitoring(self): + """切换监控状态""" + self.is_monitoring = not self.is_monitoring + + if self.is_monitoring: + # 开启监控时确保图片已加载 + if self.target_image is None: + if not self.load_target_image(): + self.is_monitoring = False + print("❌ 无法加载目标图片,监控未开启") + return + + status = "🟢 开启" if self.is_monitoring else "🔴 关闭" + print(f"监控状态: {status}") + + if self.is_monitoring: + print("开始监控屏幕,寻找目标图片...") + + def on_press(self, key): + """键盘按下事件""" + if key == self.hotkey: + self.toggle_monitoring() + elif key == keyboard.Key.esc: + # ESC 键退出程序 + print("退出程序") + os._exit(0) + + def check_mac_permissions(self): + """检查 macOS 权限""" + try: + # 尝试截图来测试权限 + test_screenshot = pyautogui.screenshot() + print("✅ 屏幕录制权限: 已授权") + return True + except Exception: + print("❌ 屏幕录制权限: 未授权") + print("请前往: 系统设置 > 隐私与安全性 > 屏幕录制") + print("为终端或您使用的 IDE 开启屏幕录制权限") + return False + + def start(self): + """启动程序""" + print("🖥️ macOS 图像点击工具") + print("=" * 40) + + # 检查权限 + if not self.check_mac_permissions(): + return + + # 查找并加载目标图片 + if not self.load_target_image(): + print("请确保项目根目录有图片文件 (png, jpg, jpeg)") + return + + print(f"🎯 目标图片: {os.path.basename(self.target_image_path)}") + print(f"📏 相似度阈值: {self.confidence_threshold}") + print(f"🎹 控制快捷键:") + print(f" - F8: 开启/关闭监控") + print(f" - ESC: 退出程序") + print("=" * 40) + print("等待指令...") + + # 启动监控线程 + monitor_thread = threading.Thread(target=self.monitoring_loop, daemon=True) + monitor_thread.start() + + # 启动键盘监听 + try: + with keyboard.Listener(on_press=self.on_press) as listener: + listener.join() + except KeyboardInterrupt: + print("\n程序被用户中断") + except Exception as e: + print(f"程序出错: {e}") + +if __name__ == "__main__": + # 确保程序在 macOS 上运行 + import platform + if platform.system() != 'Darwin': + print("警告: 这个工具是针对 macOS 优化的") + print("但检测到您正在运行:", platform.system()) + print("继续运行可能遇到兼容性问题") + + response = input("是否继续? (y/n): ") + if response.lower() != 'y': + exit() + + clicker = MacImageClicker() + clicker.start() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 0000000..af59379 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,21 @@ +MouseInfo==0.1.3 +numpy==2.2.6 +opencv-python==4.12.0.88 +pillow==11.3.0 +PyAutoGUI==0.9.54 +PyGetWindow==0.0.9 +PyMsgBox==2.0.1 +pynput==1.8.1 +pyobjc-core==11.1 +pyobjc-framework-ApplicationServices==11.1 +pyobjc-framework-Cocoa==11.1 +pyobjc-framework-CoreText==11.1 +pyobjc-framework-Quartz==11.1 +pyperclip==1.11.0 +PyRect==0.2.0 +PyScreeze==1.0.1 +pytweening==1.2.0 +rubicon-objc==0.5.2 +setuptools==78.1.1 +six==1.17.0 +wheel==0.45.1 diff --git a/target_img.png b/target_img.png new file mode 100644 index 0000000000000000000000000000000000000000..57fa6cff53e51e65e3d79894fbf9d634e6579791 GIT binary patch literal 4258 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