{"id":13584303,"url":"https://github.com/jayqizone/Surge-AppleScript","last_synced_at":"2025-04-07T01:31:58.452Z","repository":{"id":201762993,"uuid":"130559211","full_name":"jayqizone/Surge-AppleScript","owner":"jayqizone","description":"用 AppleScript 操纵 Surge 菜单，提供 Keyboard Maestro 宏样例","archived":false,"fork":false,"pushed_at":"2018-05-30T08:33:27.000Z","size":314,"stargazers_count":27,"open_issues_count":1,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-08T07:56:20.813Z","etag":null,"topics":["applescript","jxa","keyboard-maestro","surge"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jayqizone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-04-22T10:59:22.000Z","updated_at":"2025-01-24T00:45:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"53524a09-87fd-44b5-b2e2-e1f94da7817b","html_url":"https://github.com/jayqizone/Surge-AppleScript","commit_stats":null,"previous_names":["jayqizone/surge-applescript"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jayqizone%2FSurge-AppleScript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jayqizone%2FSurge-AppleScript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jayqizone%2FSurge-AppleScript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jayqizone%2FSurge-AppleScript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jayqizone","download_url":"https://codeload.github.com/jayqizone/Surge-AppleScript/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247578013,"owners_count":20961216,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["applescript","jxa","keyboard-maestro","surge"],"created_at":"2024-08-01T15:04:09.474Z","updated_at":"2025-04-07T01:31:53.412Z","avatar_url":"https://github.com/jayqizone.png","language":null,"readme":"# Surge-AppleScript\n\n[Surge](https://www.nssurge.com) 是知名的 Mac \u0026 iOS 网络分析及代理工具\n\n但是高频功能总是需要点击菜单，并不高效。囿于当前还没有官方接口可资利用，我们可以采用 macOS 原生操纵 App 界面元素的方法，将这些功能绑定到全局快捷键或者融入到个人工作流中\n\n\u003e 需要给 Keyboard Maestro Engine 赋予辅助功能权限，其它工具同\n\n![辅助功能权限](https://raw.githubusercontent.com/jayqizone/Surge-AppleScript/master/images/accessibility.png)\n\n## 1. 调用菜单中已绑定快捷键的功能\n\n最简单的，是调用菜单中已经存在的快捷键，这只需要先展开菜单，然后模拟按键即可\n\n但这第一步用常规的方法时却遇到问题，在 AppleScript 中用 `click` 动作并不能点开该菜单：\n\n```applescript\ntell application \"System Events\"\n\ttell process \"Surge\"\n\t\tclick menu bar item 1 of menu bar 2 -- 无效\n\tend tell\nend tell\n```\n\n经过研究，发现可以用 `perform action \"AXShowMenu\"` 解决，于是最终脚本如下：\n\n```applescript\ntell application \"System Events\"\n\ttell process \"Surge\"\n\t\ttell menu bar item 1 of menu bar 2\n\t\t\tperform action \"AXShowMenu\"\n\t\t\tkeystroke \"d\" using command down -- 模拟按下 ⌘+D，对应「控制台」功能\n\t\tend tell\n\tend tell\nend tell\n```\n\n## 2. 点选菜单中的功能\n\n关于 AppleScript 的 UI 操作，Surge 相比其它 App，有两点比较特殊，容易踩坑：\n1. 必须先展开菜单栏，才能获取菜单元素\n2. 如果还有子菜单，要逐层展开，不能只用一次 `click` 直击目标菜单项\n\n第一个问题之前已经解决了，第二个问题只要逐次点击即可：\n\n```applescript\ntell application \"System Events\"\n\ttell process \"Surge\"\n\t\ttell menu bar item 1 of menu bar 2\n\t\t\tperform action \"AXShowMenu\"\n\t\t\ttell menu item \"策略组名称\" of menu 1 to {click, click menu item \"节点名称\" of menu 1} -- 点选「某策略组 -\u003e 某节点」\n\t\tend tell\n\tend tell\nend tell\n```\n\n自然，前述模拟按下快捷键调用的功能，也可以换用此方式实现\n\n## 3. 例外：出站模式\n\n虽然已经解决了一些前置问题，但是特殊情况总是存在的\n\n同样具有子菜单，「策略组 -\\\u003e 节点」、「切换配置」，都可以用前面的方式操作，唯独「出站模式」，其子菜单项，再次不响应 `click` 动作。这次不可用 `perform action \"AXShowMenu\"` 解决，尝试使用其它预定义的 action（如 AXPress、AXPick 等）也不奏效，只能换其它思路\n\n### 3.1 第三方工具辅助点击\n\n一个想法是，用第三方工具，帮我们完成这最后一步模拟鼠标点击的操作，那么我们需要的，就只是目标菜单项的坐标，这很容易：\n\n```applescript\ntell application \"System Events\"\n\ttell process \"Surge\"\n\t\ttell menu bar item 1 of menu bar 2\n\t\t\tperform action \"AXShowMenu\"\n\t\t\ttell menu item 1 of menu 1 -- menu item 1 是菜单第一项，即「出站模式」\n\t\t\t\tclick\n\t\t\t\tposition of menu item 1 of menu 1 -- menu item 1 是子菜单第一项，即「直连」，因为分割线的缘故，「全局」和「规则」分别对应 menu item 3 和 5\n\t\t\tend tell\n\t\tend tell\n\tend tell\nend tell\n```\n\n而这个「第三方工具」，其实 [Keyboard Maestro](https://www.keyboardmaestro.com/main/) 就可以胜任，如图：\n\n![获取菜单项坐标并点击](https://raw.githubusercontent.com/jayqizone/Surge-AppleScript/master/images/outbound.png)\n\n### 3.2 系统原生框架编程\n\n当然还有一个愿望：原生实现最好\n\n思路还是一样的：替代 `Click`，用更「底层」的方式模拟点击操作\n\n联合使用 Objective-C，也是可以做到的，只是有些破坏 AppleScript 使用自然语义编程通俗易懂的优点。下面用 JXA（AppleScript 孪生兄弟）给出一个实现：\n\n```js\n// Objective-C Bridge\nObjC.import('AppKit');\n\n// 获取鼠标原始坐标\nvar event = $.CGEventCreate($());\nvar orig = $.CGEventGetLocation(event);\n$.CFRelease(event);\n\n// 展开菜单。注意：JavaScript 中下标从 0 开始\nvar menu = Application('System Events').processes.Surge.menuBars[1].menuBarItems[0];\nmenu.actions.AXShowMenu.perform();\n// 获取目标菜单项的坐标并点击。注意：最后一个 menuItems 的索引应为 0、2、4，对应「直连」、「全局」、「规则」\nclickMouse(...menu.menus[0].menuItems[0].click().menus[0].menuItems[4].position());\n\n// 恢复鼠标原始坐标\nmoveMouse(orig.x, orig.y);\n\n// 点击鼠标\nfunction clickMouse(x, y) {\n\tlet event = $.CGEventCreateMouseEvent($(), $.kCGEventLeftMouseDown, $.CGPointMake(x, y), $.kCGMouseButtonLeft);\n\t$.CGEventPost($.kCGHIDEventTap, event);\n\t// 少许延迟\n\tdelay(0.1)\n\t$.CGEventSetType(event, $.kCGEventLeftMouseUp);\n\t$.CGEventPost($.kCGHIDEventTap, event);\n\t$.CFRelease(event);\n}\n\n// 移动鼠标\nfunction moveMouse(x, y) {\n\tlet event = $.CGEventCreateMouseEvent($(), $.kCGEventMouseMoved, $.CGPointMake(x, y), $.kCGMouseButtonLeft);\n\t$.CGEventPost($.kCGHIDEventTap, event);\n\t$.CFRelease(event);\n}\n```\n\nJXA 在 Shell 中的调用方式是 `osascript -l JavaScript xxx.js`；在 Keyboard Maestro 中是：\n\n![JXA 联合 ObjC 框架](https://raw.githubusercontent.com/jayqizone/Surge-AppleScript/master/images/jxa.png)\n","funding_links":[],"categories":["Others"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjayqizone%2FSurge-AppleScript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjayqizone%2FSurge-AppleScript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjayqizone%2FSurge-AppleScript/lists"}