iTerm2 Python API 实战:用 CustomControlSequenceMonitor 定义自定义控制序列

发布时间:2026/9/21 18:52:46
iTerm2 Python API 实战:用 CustomControlSequenceMonitor 定义自定义控制序列
桌面应用AI 应用【免费下载链接】iTerm2iTerm2 is a terminal emulator for Mac OS X that does amazing things.项目地址https://gitcode.com/gh_mirrors/it/iTerm2点击查看免费下载iTerm2 的 Python API 提供了一套事件驱动event-driven的脚本机制其中iterm2.CustomControlSequenceMonitor允许你为自定义的 OSC 1337 控制序列注册监听器把终端输出中出现的特定字符串转化为可编程动作——例如收到^create-window$就新建一个窗口。本文以 api/library/python/iterm2/docs/customcontrol.rst 为核心骨架结合 Python 库源码、官方教程与 iTerm2 本体实现讲解该类的完整用法、安全设计、底层订阅机制与多会话进阶实践读完你可以直接写出可运行的自定义控制序列守护进程。一、什么是自定义控制序列终端模拟器通过带内in-band信号——控制序列control sequence——驱动光标移动、清屏、改色等动作。iTerm2 在此基础上开放了Custom Control Sequences自定义控制序列脚本可以定义自己的控制序列并在终端收到该序列时执行自定义动作。自定义控制序列的线上格式为OSC 1337 ; Customididentity:payload ST在 bash 命令行中可用printf直接发送\033是 ESC\a是 BEL即 ST 的一种表示printf \033]1337;Customid%s:%s\a shared-secret create-window其中ididentity发送方身份标识同时充当共享密钥shared secret用来防止未经授权的程序触发你的脚本payload紧随冒号后的业务载荷由监听器用正则表达式匹配。这一格式在 Python 库侧得到了确认iterm2.notifications.async_subscribe_to_custom_escape_sequence_notification的 docstring 明确写着 The escape sequence is OSC 1337 ; Customididentity:payload ST见 notifications.py。二、核心类CustomControlSequenceMonitor文档 customcontrol.rst 通过 Sphinx 的automodule/autoclass指令将 iterm2/customcontrol.py 的类与文档直接绑定。该类全名iterm2.CustomControlSequenceMonitor其构造参数如下参数类型含义connectioniterm2.connection.Connection与 iTerm2 的连接对象来自iterm2.run_forever(main)/run_until_complete传入的connectionidentitystr发送方身份标识必须与控制序列中的id完全一致充当共享密钥regexstr正则表达式用于在 payload 上执行re.search命中时把re.Match对象交给async_get()返回session_idOptional[str]要监听的会话 ID传None表示监听所有会话包括尚未创建的CustomControlSequenceMonitor是一个asyncio 异步上下文管理器async context manager进入async with时完成订阅注册退出时自动注销订阅。它暴露的唯一公开方法是async_get()。async_get()阻塞式取回匹配结果async def async_get(self) - typing.Match: Blocks until a matching control sequence is returned. :returns: A re.Match produced by searching the control sequences payload with the regular expression this object was initialized with. return await self.__queue.get()async_get()会一直阻塞直到收到一条身份匹配且 payload 命中正则的控制序列然后返回re.Match对象。也就是说匹配条件有两层——identity必须与sender_identity完全相等regex必须对 payload 命中re.search语义非整串锚定。内部实现要点从 customcontrol.py 的__aenter__可以看出注册机制定义内部回调internal_callback(_connection, notification)先比较notification.sender_identity与self.__identity不一致直接丢弃再用re.search(self.__regex, notification.payload)匹配命中则await self.__queue.put(match)调用iterm2.notifications.async_subscribe_to_custom_escape_sequence_notification(self.__connection, internal_callback, self.__session_id)完成订阅并把返回的 token 保存起来__aexit__中用该 token 调用async_unsubscribe注销若抛出SubscriptionException则静默忽略。底层通知类型为 protobuf 的CustomEscapeSequenceNotification携带session会话 GUID、senderIdentity与payload三个字段可在 api_pb2.pyi 中查到该消息类型。三、第一个完整示例收到控制序列就新建窗口官方教程 daemons.rst 给出了一个长驻守护进程Long-Running Daemon的完整模板。当你在 iTerm2 的Scripts菜单中新建脚本并选择Long-Running Daemon时默认生成的就是这个示例#!/usr/bin/env python3 import iterm2 async def main(connection): async with iterm2.CustomControlSequenceMonitor( connection, shared-secret, r^create-window$) as mon: while True: match await mon.async_get() await iterm2.Window.async_create(connection) iterm2.run_forever(main)运行步骤把脚本放入~/Library/Application Support/iTerm2/Scripts/AutoLaunch目录成为 AutoLaunch 脚本重启 iTerm2 后自动启动也可随时从Scripts菜单手动运行在任意终端执行printf \033]1337;Customid%s:%s\a shared-secret create-window脚本收到匹配的控制序列后调用iterm2.Window.async_create(connection)新建一个窗口。关于这段代码有几个关键点值得展开身份匹配是硬条件控制序列里idshared-secret必须与构造参数shared-secret完全一致否则回调直接返回while True不能省略上下文管理器进入后控制流进入循环体async_get()每次阻塞等待下一条匹配序列实现持续服务iterm2.run_forever(main)让脚本永不退出即使main返回注册的控制序列仍然保持生效脚本继续在后台处理序列——这正是守护进程的含义该示例对应的原始文档为 create_window.rst。四、多会话进阶识别来源会话并动态拆分窗格ccs.rst 提供了更贴近实战的示例为每个新会话单独注册一个监听器收到^split$后把该会话拆分出窗格从而在回调中得知控制序列来自哪个会话。import asyncio import iterm2 tasks {} async def main(connection): app await iterm2.async_get_app(connection) tmtask asyncio.create_task(monitor_termination(connection)) async with iterm2.EachSessionOnceMonitor(app) as mon: while True: session_id await mon.async_get() print(session_id) session app.get_session_by_id(session_id) task asyncio.create_task(monitor_ccs(connection, session, session_id)) tasks[session_id] task async def monitor_ccs(connection, session, session_id): try: print(fMonitor {session_id}) async with iterm2.CustomControlSequenceMonitor( connection, shared-secret, r^split$, session_id) as mon: while True: match await mon.async_get() print(fWill split {session_id}) await session.async_split_pane() except Exception as e: print(fException in {session_id}: {e}) async def monitor_termination(connection): global tasks try: async with iterm2.SessionTerminationMonitor(connection) as mon: while True: print(Waiting for termination) session_id await mon.async_get() print(Session {} closed.format(session_id)) task tasks[session_id] del tasks[session_id] print(Cancel task) task.cancel() print(await task) await task print(End of loop) except Exception as e: print(fException in {session_id}: {e}) iterm2.run_forever(main)运行期间在当前会话中执行printf \033]1337;Customid%s:%s\a shared-secret split即可看到该会话被拆分为左右两个窗格。这个示例揭示了三个进阶用法按会话定向监听把session_id传给CustomControlSequenceMonitor控制序列触发时session对象就是来源会话可直接调用async_split_pane()等会话级 API并发多个监听器每个monitor_ccs都是独立 asyncio 任务通过asyncio.create_task并发运行避免被某个while True阻塞生命周期管理monitor_termination监听会话终止会话关闭后task.cancel()取消对应的监听任务防止泄漏。五、安全设计为什么必须用 identity 共享密钥ccs 示例文档明确说明了identity的安全意义Theshared-secretstring is used to prevent untrusted code from invoking your function. For example, if youcata text file, it could include escape sequences, but they wont work unless they contain the proper secret string.即cat一个包含转义序列的文本文件时文件里的序列不会生效除非它恰好包含正确的密钥字符串。因为iTerm2 会把终端收到的自定义控制序列id与payload转发给 Python API 进程Python 侧的internal_callback首先检查notification.sender_identity ! self.__identity不等则直接丢弃customcontrol.py因此未经授权的程序打印出的序列无法命中监听器。这是整条链路的第二道防线。第一道防线在 iTerm2 本体高级设置中的disablePotentiallyInsecureEscapeSequences默认NO可以一次性禁用包括 RemoteHost、StealFocus、CopyToClipboard、SetBackgroundImageFile、内联图片等在内的潜在不安全转义序列定义于 iTermAdvancedSettingsModel.m。在查看不受信任的内容时建议开启该选项降低攻击面。六、底层原理从终端到 Python 回调的完整链路1. iTerm2 本体解析并转发Objective-C 侧当会话收到自定义控制序列时PTYSession.m 的screenDidReceiveCustomEscapeSequenceWithParameters:payload:负责处理- (void)screenDidReceiveCustomEscapeSequenceWithParameters:(NSDictionaryNSString *, NSString * *)parameters payload:(NSString *)payload { if (_eventTriggerEvaluator.hasCustomEscapeSequenceTrigger) { NSString *identifier parameters[id] ?: ; iTermEventCustomEscapeSequenceInfo *info [[iTermEventCustomEscapeSequenceInfo alloc] initWithIdentifier:identifier payload:payload ?: ]; [_eventTriggerEvaluator customEscapeSequenceWithInfo:info]; } ITMNotification *notification [[[ITMNotification alloc] init] autorelease]; notification.customEscapeSequenceNotification [[[ITMCustomEscapeSequenceNotification alloc] init] autorelease]; notification.customEscapeSequenceNotification.session self.guid; notification.customEscapeSequenceNotification.senderIdentity parameters[id]; notification.customEscapeSequenceNotification.payload payload; [_customEscapeSequenceNotifications enumerateKeysAndObjectsUsingBlock: ^(id _Nonnull key, ITMNotificationRequest * _Nonnull obj, BOOL * _Nonnull stop) { [[iTermAPIHelper sharedInstance] postAPINotification:notification toConnectionKey:key]; }]; }从源码可以看出iTerm2 不仅向 Python API 广播CustomEscapeSequenceNotification携带sessionGUID、senderIdentity、payload还会把该序列喂给事件触发器Event Trigger系统——即 iTerm2 原生触发器同样可以响应自定义控制序列iTermTriggerMatchTypeEventCustomEscapeSequence见 ITAddressBookMgr.h。2. Python 侧订阅notifications 模块Python 库通过_async_subscribe(connection, True, iterm2.api_pb2.NOTIFY_ON_CUSTOM_ESCAPE_SEQUENCE, callback, sessionsession)向 iTerm2 注册订阅notifications.py返回的 token 用于退订。CustomControlSequenceMonitor在此之上封装了身份过滤 → 正则匹配 → 放入asyncio.Queue→async_get()阻塞取出的完整流程。3. 模块导出CustomControlSequenceMonitor在 iterm2/init.py 中导出因此脚本中可直接以iterm2.CustomControlSequenceMonitor引用无需额外 import 子模块。七、进阶技巧与注意事项1. 用正则捕获组传递参数如果希望 payload 携带更多信息比如创建 N 个窗口可以利用正则捕获组构造时使用带分组表达式例如r^create-window-(\d)$然后在async_get()返回的re.Match上通过match.group(1)取到数量。教程 daemons.rst 对此有明确说明you could use the regular expression matcher to capture that value in a capture group and retrieve it from the matcher in the callback。2. 同时注册多个监听器必须并发async with的循环体是while True无限循环若想同时监听两条不同的控制序列必须为每个监听器创建独立 taskasync def wrapper(): async with iterm2.CustomControlSequenceMonitor( connection, identity, regex) as mon: while True: DoSomething(await mon.async_get()) asyncio.create_task(wrapper()) # Define more wrappers and create more tasks否则程序会卡在第一个while True中永远无法注册第二个监听器。3. 会话级监听与全局监听的取舍session_idNone监听所有会话含未来新建的适合全局服务如任何会话都能创建窗口session_id具体ID只监听指定会话适合把动作绑定到来源会话如拆分窗格、向该会话写回数据。4. 类似的事件型上下文管理器CustomControlSequenceMonitor只是 iTerm2 Python API 中众多事件上下文管理器之一。同类机制还包括iterm2.FocusMonitor、iterm2.KeystrokeMonitor、iterm2.NewSessionMonitor、iterm2.PromptMonitor、iterm2.SessionTerminationMonitor、iterm2.VariableMonitor、iterm2.LayoutChangeMonitor等。掌握了自定义控制序列的订阅-阻塞-取回模式即可举一反三地使用其他事件。结语自定义控制序列是 iTerm2 脚本化的暗号机制终端里任何程序只要打印出携带正确密钥的 OSC 1337 序列就能驱动你的 Python 脚本执行任意动作。借助iterm2.CustomControlSequenceMonitor你可以用不到十行代码实现打印即触发的自动化——无论是创建窗口、拆分窗格还是携带参数执行更复杂的操作。本文所述的 API 文档入口为 customcontrol.rst配套教程与完整示例分别位于 tutorial/daemons.rst、examples/ccs.rst 与 examples/create_window.rst源码细节可继续翻阅 iterm2/customcontrol.py 与 sources/PTYSession/PTYSession.m。赞分享桌面应用AI 应用【免费下载链接】iTerm2iTerm2 is a terminal emulator for Mac OS X that does amazing things.项目地址https://gitcode.com/gh_mirrors/it/iTerm2点击查看免费下载相关推荐iTerm2 自定义转义序列Custom Control Sequence实战基于 Python API 构建会话级控制通道iTerm2 自定义转义序列Custom Control Sequence实战基于 Python API 构建会话级控制通道 导读 自定义转义序列Cus桌面应用AI 应用iTerm2 Python API 守护进程Daemon完全指南用 AutoLaunch 脚本与自定义控制序列构建常驻服务iTerm2 Python API 守护进程Daemon完全指南用 AutoLaunch 脚本与自定义控制序列构建常驻服务 本篇技术指南聚焦 iTerm2桌面应用AI 应用如何在浏览器中运行Asciidoctor.js构建无服务器文档预览系统如何在浏览器中运行Asciidoctor.js构建无服务器文档预览系统 Asciidoctor.js是AsciiDoc的JavaScript实现它允许你在浏开发工具上一篇Kubepug离线环境部署在无网络环境中构建完整的API废弃检测系统下一篇OAID/Tengine模型可视化工具Netron使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考