
换台电脑脚本就全废KeymouseGo 怎么把录制变成一套可移植的事件流水线【免费下载链接】KeymouseGo类似按键精灵的鼠标键盘录制和自动化操作 模拟点击和键入 | automate mouse clicks and keyboard input项目地址: https://gitcode.com/gh_mirrors/ke/KeymouseGo你花一晚上录好的一套鼠标键盘操作脚本换到同事那台 2K 显示器、150% 缩放的机器上一跑——所有点击全飘了。这个场景几乎每个玩过录制回放类工具的人都经历过。KeymouseGo 作为一款开源跨平台鼠标键盘录制与自动化工具基于 Python PySide6把录下来能用变成了录下来能在别处用。这篇文章不讲它怎么好用只讲它是怎么把坐标、事件、时序这三件最容易翻车的工程问题逐一摁住的。坐标为什么会飘问题出在 DPI不在分辨率先说结论脚本跨机器失效九成是坐标系统的问题而坐标系统的问题里DPI 缩放比分辨率更阴险。分辨率差异是显性的——你 1920×1080对方 2560×1440像素坐标直接错位这谁都能想到。但 DPI 缩放是隐性的Windows 默认给高分屏做 125%/150% 的全局缩放逻辑坐标和物理像素之间隔了一层。你录的时候拿到的是逻辑坐标回放时若直接当物理坐标用GetSystemMetrics返回的屏幕尺寸和你看到的不一致点击自然偏移。KeymouseGo 的 Windows 事件实现里有一行不起眼的代码是整套方案的基石user32.SetProcessDPIAware() SW, SH user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)读法提示进程声明自己感知 DPI 之后拿到的屏幕尺寸才是真实像素否则 Windows 会塞给你一套被缩放过的假尺寸坐标转换从源头就是错的。再看它对坐标的处理思路其实是一套存取分离的策略录制时存相对值回放时算绝对值。三套坐标方案各有取舍直接对比方案实现原理跨机器可靠性结论/取舍绝对像素坐标录制时原样存(x, y)换分辨率必废只能同一台机器自录自放放弃可移植性相对百分比坐标存(x/宽, y/高)分辨率变化可适应简单可靠是 Linux/macOS 与单屏 Windows 的默认方案归一化坐标0~65535回放时用mouse_event的绝对模式映射兼容 DPI 缩放的 Win10 环境Windows 下的增强方案代价是代码分支变多KeymouseGo 的取舍很务实跨平台实现UniversalEvents.py走相对百分比路线Windows 实现则优先用归一化坐标打通MOUSEEVENTF_ABSOLUTE。这套录制存比例、回放做换算的思路正是它敢说脚本可移植的底气。别小看那个 0~65535一条被压扁又拉开的坐标轴如果你点开 Windows 事件类看执行逻辑会发现它没有简单调用SetCursorPos而是把坐标塞进了mouse_eventif isinstance(x, int) and isinstance(y, int): if numofmonitors 1: win32api.SetCursorPos([x, y]) else: nx int(x * 65535 / SW) ny int(y * 65535 / SH) win32api.mouse_event(win32con.MOUSEEVENTF_ABSOLUTE | win32con.MOUSEEVENTF_MOVE, nx, ny, 0, 0)读法提示MOUSEEVENTF_ABSOLUTE要求坐标落在 0~65535 的归一化区间内Windows 内部再按当前缩放比例把它映射回真实屏幕——这相当于把DPI 适配这件事甩给了系统层而不是自己在像素层面硬算。多显示器时退回SetCursorPos也很有意思MOUSEEVENTF_ABSOLUTE在多屏下映射的是主屏坐标副屏坐标会算错所以这里按numofmonitors 1做分支。代码里处处是这种按环境切策略的痕迹而不是假设一个完美世界。跨平台的真相不是适配系统是抹平系统说到跨平台多数人会想到分别调用 Win32 / Quartz / X11 的 API。KeymouseGo 的做法更彻底在事件层把平台差异整个抹掉上层只认一种事件。三种平台的输入机制差异巨大平台底层事件来源录制捕获方式回放注入方式结论/取舍WindowsWin32 消息循环低层鼠标钩子 WH_MOUSE_LLmouse_event/keybd_event精度高、可捕获侧键但代码平台耦合深macOSQuartz Event Servicespynput 监听pyautogui 注入简洁统一牺牲部分底层控制力LinuxX11 / Waylandpynput 监听pyautogui 注入与 macOS 共用一套实现覆盖面广所有事件在Event.py里被抽象成一个统一对象——delay、event_type、action_type、action四个字段外加一个execute()方法class Event(metaclassABCMeta): def __init__(self, content: Dict[str, Any]): for key in [delay, event_type, action_type, action]: setattr(self, key, content[key]) abstractmethod def execute(self, thdNone): pass读法提示这个抽象类把事件长什么样和事件怎么执行彻底分开录制端只负责拼装字典回放端只负责调用execute()平台差异全部下沉到子类。真正的分发逻辑更简单粗暴——Event/__init__.py里根据platform.system()一行import决定用哪个子类把WindowsEvent或UniversalEvent统一暴露成ScriptEvent。录制器、执行器、插件全都只认ScriptEvent这个名字谁也不需要关心底下是 Win32 还是 pynput。这个模式的价值在于以后要支持新平台只写一套事件子类和一套录制器其余代码零改动。时序精度不是sleep 得准而是睡得能被叫醒录制回放工具最容易被低估的难点是时序。录制时每个事件记录距上个事件的间隔回放时原样复现。听起来简单但有两个坑一是录制端鼠标移动事件太密全录下来脚本体积爆炸、回放卡顿二是回放端如果只用time.sleep用户点暂停/停止后线程无法立刻响应只能干等剩余延时。录制端的降噪逻辑在WindowsRecorder.py里一目了然delay globalv.current_ts() - globalv.latest_time mouse_move_interval_ms globalv.mouse_interval_ms or 999999 if action_type mouse move and delay mouse_move_interval_ms: return True读法提示鼠标移动事件被设置了节流阈值——间隔小于阈值的移动直接丢弃只保留关键轨迹点录制出的脚本体积和回放负载都能降一个量级且不会影响最终点击位置。回放端的精髓则在RunScriptMeta里——它把sleep换成了条件变量等待def sleep(self, msecs: int): mutex.lock() cond.wait(mutex, QDeadlineTimer(int(msecs))) mutex.unlock() def resume(self): mutex.lock() cond.wakeAll() mutex.unlock()读法提示cond.wait带超时就是睡到点自动醒而wakeAll能让暂停/停止信号立刻中断睡眠——所以暂停响应是毫秒级的而不是等当前延时走完。这是把死等变成可打断的等工程上差一个量级的体验。脚本不是事件数组是一张跳转图普通录制工具把脚本存成一串数组回放时从头扫到尾遇到循环就傻眼。KeymouseGo 的Parser.py把 JSON5 脚本在加载时倒序编织成一张链表式的控制流图next_object指向下一条next_object_if_false留给条件分支goto通过 label 索引直接跳转。elif object_type if: current_object.next_object \ ScriptParser.link_objects(content[do], target_object, label_maps, pending_dict) current_object.next_object_if_false \ ScriptParser.link_objects(content[else], target_object, label_maps, pending_dict)读法提示if分支被拆成两条链分别递归构建回放时执行器只需沿next_object或next_object_if_false走判断逻辑天然落位——脚本文件里没有一行控制流代码控制流完全由链表结构表达。老版本脚本纯数组格式由LegacyParser兜底解析同样倒序建成链表。这意味着新引擎 旧脚本能共存升级工具不报废存量资产这是很多同类工具做不到的向后兼容。插件化把改源码降级成丢个文件夹坐标漂移这种问题靠内置策略永远堵不干净——总有人有怪异的显示器组合或缩放需求。KeymouseGo 的答案不是把代码写得更全而是开放插口PluginManager启动时扫描plugins目录读取每个子目录里的manifest.json5用SourceFileLoader动态加载插件类并把插件注册的函数收进一张全局字典loader SourceFileLoader(manifest[plugin_class], os.path.join(entry.path, manifest[entry])) plugin_module loader.load_module() plugin_class getattr(plugin_module, manifest[plugin_class]) PluginManager.plugins.append(plugin_class(manifest))读法提示manifest声明了入口文件和类名加载器按图索骥完成实例化——插件与主程序之间没有任何编译期耦合新增一个插件本质就是丢一个带清单的文件夹。脚本侧通过call字段按名字调用插件函数执行器遇到if类型的节点时直接把判断逻辑交给插件返回布尔值。这套设计把坐标自适应图像识别条件循环这些高级玩法全部挡在核心之外核心引擎保持最小扩展性交给生态——典型的微内核取向。回到开头那个换电脑的脚本现在能活了吗回看开头那个换台电脑全飘的场景KeymouseGo 给出的答案是一整套组合拳录制时存相对坐标多显示器下除外Windows 回放时用归一化坐标让系统层消化 DPI 缩放Linux/macOS 靠屏幕比例换算兜底再配合可打断的时序控制和插件化的坐标兜底方案。所以实操建议也顺理成章录脚本时保持单显示器、100% 缩放环境让录制端产生干净的比例坐标如果必须在多屏环境录制优先选能记录屏幕尺寸信息的方案遇到极端环境写个坐标转换插件远比改核心代码现实。工具的价值不在于能把操作录下来而在于录下来的东西经得起换环境考验。KeymouseGo 用事件抽象、归一化坐标、可打断延时和插件机制把桌面自动化从一次性宏做成了可移植的事件流水线。这份工程取舍比任何自动化效率翻倍的宣传都更值得开发者细读。【免费下载链接】KeymouseGo类似按键精灵的鼠标键盘录制和自动化操作 模拟点击和键入 | automate mouse clicks and keyboard input项目地址: https://gitcode.com/gh_mirrors/ke/KeymouseGo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考