|
@@ -0,0 +1,217 @@
|
|
|
|
|
+import pyautogui
|
|
|
|
|
+import cv2
|
|
|
|
|
+import numpy as np
|
|
|
|
|
+import time
|
|
|
|
|
+import threading
|
|
|
|
|
+from pynput import keyboard
|
|
|
|
|
+from pynput.mouse import Button
|
|
|
|
|
+import os
|
|
|
|
|
+import subprocess
|
|
|
|
|
+
|
|
|
|
|
+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.5
|
|
|
|
|
+
|
|
|
|
|
+ # 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
|
|
|
|
|
+
|
|
|
|
|
+ # 在 macOS 上使用 pyautogui 点击
|
|
|
|
|
+ current_pos = pyautogui.position()
|
|
|
|
|
+ pyautogui.moveTo(center_x, center_y, duration=0.1)
|
|
|
|
|
+ pyautogui.click()
|
|
|
|
|
+
|
|
|
|
|
+ # 可选:移回原位置
|
|
|
|
|
+ # pyautogui.moveTo(current_pos.x, current_pos.y, duration=0.1)
|
|
|
|
|
+
|
|
|
|
|
+ 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()
|