iTerm2 Python API 控制技能
此技能提供通过 iTerm2 官方 Python API(iterm2 包)编程控制 iTerm2 终端的完整能力,涵盖窗口/标签/会话管理、命令执行、屏幕内容读取、配置修改、事件监控等所有核心功能。
环境准备
安装 iterm2 包
iTerm2 提供内置 Python 环境和 iterm2 包:
# 通过 iTerm2 内置 Python 环境安装(推荐)
~/Library/ApplicationSupport/iTerm2/iterm2env/versions/*/bin/pip3 install iterm2
# 或者通过标准 pip 安装
pip3 install iterm2
启用 Python API
在 iTerm2 中:Preferences > General > Magic > Enable Python API(需要打开此开关)。
脚本存放位置
- 手动执行脚本:
~/Library/Application Support/iTerm2/Scripts/ - 自动启动守护进程:
~/Library/Application Support/iTerm2/Scripts/AutoLaunch/
运行脚本的方式
# 方式一:通过 iTerm2 菜单 Scripts > <脚本名>
# 方式二:命令行运行(需确保无 PYTHONPATH 干扰)
~/Library/ApplicationSupport/iTerm2/iterm2env/versions/*/bin/python3 my_script.py
# 方式三:通过 it2run 工具(绕过权限弹窗)
/Applications/iTerm.app/Resources/it2run my_script.py
核心架构模式
基础脚本模板
iTerm2 Python API 基于 asyncio 异步架构,所有 API 调用都是 async/await:
#!/usr/bin/env python3
import iterm2
async def main(connection):
# connection 是与 iTerm2 进程的 WebSocket 连接
app = await iterm2.async_get_app(connection)
# 在此执行操作...
# 执行完 main() 后退出
iterm2.run_until_complete(main)
长期运行的守护进程模板
#!/usr/bin/env python3
import iterm2
async def main(connection):
app = await iterm2.async_get_app(connection)
# 注册事件监听器、RPC 函数等...
# main() 返回后脚本继续运行
# 永不退出,持续监听事件
iterm2.run_forever(main)
async_get_app() 详解
app = await iterm2.async_get_app(connection)
# app 是单例 App 对象,提供对所有窗口/标签/会话的访问
# create_if_needed=True(默认):若单例不存在则创建
1. App 级别操作
获取 App 状态
async def main(connection):
app = await iterm2.async_get_app(connection)
# 获取当前激活的窗口
window = app.current_window # 同 current_terminal_window
# current_terminal_window 已弃用,用 current_window
# 获取所有窗口列表
windows = app.windows # 同 terminal_windows(已弃用)
# 查找特定对象
session = app.get_session_by_id("session_id", include_buried=True)
tab = app.get_tab_by_id("tab_id")
window = app.get_window_by_id("window_id")
window = app.get_window_for_tab("tab_id")
# 获取 session 对应的 window 和 tab
(window, tab) = app.get_window_and_tab_for_session(session)
# 获取埋藏的会话(buried sessions)
buried = app.buried_sessions
# 获取广播域(broadcast domains)
domains = app.broadcast_domains
# 打印整个层级结构
print(app.pretty_str())
激活 App
async def main(connection):
app = await iterm2.async_get_app(connection)
# 给 iTerm2 键盘焦点(把所有窗口提前)
await app.async_activate(raise_all_windows=True, ignoring_other_apps=False)
获取主题
async def main(connection):
app = await iterm2.async_get_app(connection)
# 返回 list,可能包含:light, dark, automatic, minimal, highContrast
theme = await app.async_get_theme()
# 例如 ["dark", "minimal"]
if "dark" in theme:
print("当前是深色主题")
App 全局变量
async def main(connection):
app = await iterm2.async_get_app(connection)
# 读取全局变量(如 effectiveTheme)
theme = await app.async_get_variable("effectiveTheme")
# 设置用户自定义全局变量(必须以 "user." 开头)
await app.async_set_variable("user.myKey", "myValue")
# 调用全局 RPC 函数
result = await iterm2.async_invoke_function(
connection,
"my_function(arg: value)",
timeout=10.0
)
从外部启动 iTerm2(需要 PyObjC)
import AppKit
import iterm2
# 启动 iTerm2 应用程序(如果未运行)
AppKit.NSWorkspace.sharedWorkspace().launchApplication_("iTerm2")
async def main(connection):
app = await iterm2.async_get_app(connection)
await app.async_activate()
# ...
iterm2.run_until_complete(main, retry=True)
# retry=True:连接失败时自动重试(适合从外部启动 iTerm2 后立即连接)
2. 窗口管理
创建新窗口
async def main(connection):
# 用默认 Profile 创建新窗口
window = await iterm2.Window.async_create(connection)
# 指定 Profile 名称
window = await iterm2.Window.async_create(connection, profile="My Profile")
# 指定启动命令
window = await iterm2.Window.async_create(connection, command="/bin/bash")
# 使用 Profile 自定义(不修改底层 Profile)
customizations = iterm2.LocalWriteOnlyProfile()
customizations.set_background_color(iterm2.Color(0, 0, 0))
window = await iterm2.Window.async_create(
connection,
profile="Default",
profile_customizations=customizations
)
if window is None:
print("窗口创建后会话立即结束")
获取当前窗口
async def main(connection):
app = await iterm2.async_get_app(connection)
window = app.current_window
if window is None:
print("没有打开的窗口")
return
print(f"当前窗口 ID: {window.window_id}")
窗口属性
async def main(connection):
app = await iterm2.async_get_app(connection)
window = app.current_window
# 获取窗口位置和大小(Frame 原点在主屏右下角)
frame = await window.async_get_frame()
# frame.origin.x, frame.origin.y, frame.size.width, frame.size.height
# 设置窗口位置和大小
new_frame = iterm2.Frame(
origin=iterm2.Point(100, 200),
size=iterm2.Size(800, 600)
)
await window.async_set_frame(new_frame)
# 全屏状态
is_fullscreen = await window.async_get_fullscreen()
await window.async_set_fullscreen(True)
# 激活窗口(给焦点,置于前台;不激活 App 本身)
await window.async_activate()
# 关闭窗口
await window.async_close() # 有确认提示
await window.async_close(force=True) # 跳过确认
# 当前激活的标签
current_tab = window.current_tab
# 所有标签列表
tabs = window.tabs
# 格式化输出窗口层级
print(window.pretty_str())
窗口变量
async def main(connection):
app = await iterm2.async_get_app(connection)
window = app.current_window
# 设置窗口用户变量(必须以 "user." 开头)
await window.async_set_variable("user.project", "my-project")
# 调用 RPC(在窗口上下文中)
result = await window.async_invoke_function(
"my_rpc(arg: value)",
timeout=5.0
)
窗口布局(重排标签)
async def main(connection):
app = await iterm2.async_get_app(connection)
windows = app.windows
if len(windows) >= 2:
w1, w2 = windows[0], windows[1]
tab_to_move = w1.current_tab
# 把 tab_to_move 移动到 w2(tabs 列表末尾)
await w2.async_set_tabs(w2.tabs + [tab_to_move])
# 注意:Tab 会从原窗口移走
3. 标签管理
创建新标签
async def main(connection):
app = await iterm2.async_get_app(connection)
window = app.current_window
# 用默认 Profile 在末尾创建新标签
tab = await window.async_create_tab()
# 指定 Profile 和位置
tab = await window.async_create_tab(
profile="My Profile",
command="/bin/zsh",
index=0 # 0=最前,None=末尾
)
# 使用 Profile 自定义
customizations = iterm2.LocalWriteOnlyProfile()
customizations.set_foreground_color(iterm2.Color(255, 255, 255))
tab = await window.async_create_tab(profile_customizations=customizations)
# Tmux 标签
# tab = await window.async_create_tmux_tab(tmux_connection)
标签属性与操作
async def main(connection):
app = await iterm2.async_get_app(connection)
window = app.current_window
tab = window.current_tab
# Tab ID
print(tab.tab_id)
# 所有会话(不含最小化的会话)
sessions = tab.sessions
# 当前激活的会话
current_session = tab.current_session
# 分割面板的树形结构根节点
root_splitter = tab.root # Splitter 对象
# 所属窗口
parent_window = tab.window
# 激活标签
await tab.async_activate(order_window_front=True)
await tab.async_select(order_window_front=True) # 已弃用,用 async_activate
# 关闭标签
await tab.async_close()
await tab.async_close(force=True)
# 设置标签标题(插值字符串,空字符串恢复默认)
await tab.async_set_title("我的标签")
# 移动标签到新窗口(仅当标签不是所在窗口唯一标签时可用)
new_window = await tab.async_move_to_window()
# 激活相邻方向的分割面板
# direction: NavigationDirection.LEFT/RIGHT/UP/DOWN
new_session_id = await tab.async_select_pane_in_direction(
iterm2.NavigationDirection.RIGHT
)
# 更新分割面板布局(先修改 session.preferred_size)
await tab.async_update_layout()
# 标签变量
value = await tab.async_get_variable("user.myKey")
await tab.async_set_variable("user.myKey", "value")
print(tab.pretty_str())
遍历所有窗口/标签/会话
async def main(connection):
app = await iterm2.async_get_app(connection)
for window in app.windows:
print(f"Window {window.window_id}")
for tab in window.tabs:
print(f" Tab {tab.tab_id}")
for session in tab.sessions:
print(f" Session {session.session_id}: {session.name}")
4. 会话管理(Session)
分割面板(Split Pane)
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 垂直分割(左右),在右侧创建新会话
right_session = await session.async_split_pane(vertical=True)
# 垂直分割(左右),在左侧创建新会话
left_session = await session.async_split_pane(vertical=True, before=True)
# 水平分割(上下),在下方创建新会话
bottom_session = await session.async_split_pane(vertical=False)
# 水平分割(上下),在上方创建新会话
top_session = await session.async_split_pane(vertical=False, before=True)
# 分割时指定 Profile
new_session = await session.async_split_pane(
vertical=True,
profile="My Profile"
)
# 分割时自定义 Profile 属性
customizations = iterm2.LocalWriteOnlyProfile()
customizations.set_background_color(iterm2.Color(30, 30, 30))
new_session = await session.async_split_pane(
vertical=True,
profile_customizations=customizations
)
创建典型的四格分割布局
async def main(connection):
app = await iterm2.async_get_app(connection)
# 获取起始会话(左下角)
bottom_left = app.current_window.current_tab.current_session
# 向右分割得到右下角
bottom_right = await bottom_left.async_split_pane(vertical=True)
# 在左下角上方分割得到左上角
top_left = await bottom_left.async_split_pane(vertical=False, before=True)
# 在右下角上方分割得到右上角
top_right = await bottom_right.async_split_pane(vertical=False, before=True)
# 激活左下角
await bottom_left.async_activate()
发送文本/命令
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 发送文本(模拟用户键入)
await session.async_send_text("echo hello\n")
# 发送文本(抑制广播,只发到本会话)
await session.async_send_text("ls -la\n", suppress_broadcast=True)
# 注入数据(模拟程序输出,不是用户输入)
await session.async_inject(b"This appears as terminal output\r\n")
# 注入 ANSI 控制序列(例如清除滚动缓冲区)
code = b'\x1b]1337;ClearScrollback\x07'
await session.async_inject(code)
会话属性
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 唯一 ID
print(session.session_id)
# 会话名称(标题)
print(session.name)
# 所属 Tab 和 Window
tab = session.tab
window = session.window
# 网格尺寸(列数×行数)
size = session.grid_size
print(f"宽:{size.width} 高:{size.height}")
# 目标尺寸(用于 tab.async_update_layout)
session.preferred_size = iterm2.Size(80, 24)
# 是否被埋藏
print(session.buried)
激活会话
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 激活会话(选中其所在 Tab,并把窗口提到前台)
await session.async_activate(select_tab=True, order_window_front=True)
# 仅激活会话(不移动窗口到前台)
await session.async_activate(order_window_front=False)
关闭与重启
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 关闭(有确认提示)
await session.async_close()
# 强制关闭(无确认)
await session.async_close(force=True)
# 重启会话
await session.async_restart()
# 仅在会话已退出时重启(如果还在运行则抛异常)
await session.async_restart(only_if_exited=True)
埋藏会话(Bury/Disinter)
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 埋藏会话(从界面隐藏,但保持运行)
await session.async_set_buried(True)
# 恢复埋藏的会话
buried_sessions = app.buried_sessions
if buried_sessions:
await buried_sessions[0].async_set_buried(False)
调整会话大小
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 设置会话网格大小(仅适用于单面板 Tab,不能在全屏窗口使用)
await session.async_set_grid_size(iterm2.Size(120, 40))
会话变量
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 读取内置会话变量
hostname = await session.async_get_variable("hostname")
username = await session.async_get_variable("username")
pwd = await session.async_get_variable("path")
pid = await session.async_get_variable("pid")
job_name = await session.async_get_variable("jobName")
# 设置用户自定义变量(必须以 "user." 开头)
await session.async_set_variable("user.project", "my-project")
value = await session.async_get_variable("user.project")
# 在会话上下文中调用 RPC
result = await session.async_invoke_function(
"my_function(arg: value)",
timeout=5.0
)
设置会话名称(标题)
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 方式一:通过 RPC 设置名称(临时)
await session.async_set_name("My Custom Session Name")
# 方式二:通过 Profile 属性设置(永久,且禁止 escape sequence 修改)
update = iterm2.LocalWriteOnlyProfile()
update.set_name("Permanent Session Name")
update.set_allow_title_setting(False) # 禁止 escape 序列修改标题
await session.async_set_profile_properties(update)
5. 读取屏幕内容
读取当前屏幕内容
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 读取可见屏幕区域
contents = await session.async_get_screen_contents()
print(f"光标位置: {contents.cursor_coord}")
print(f"可见行数: {contents.number_of_lines}")
print(f"屏幕以上行数: {contents.number_of_lines_above_screen}")
# 逐行读取
for i in range(contents.number_of_lines):
line = contents.line(i)
print(f"Line {i}: {line.string}")
print(f" 硬换行: {line.hard_eol}")
# 读取某列的字符
char = line.string_at(0)
读取历史缓冲区内容(需要 Transaction)
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# Transaction 确保读取期间终端状态不变
async with iterm2.Transaction(connection):
line_info = await session.async_get_line_info()
# line_info.overflow: 溢出(丢失)的历史行数
# line_info.scrollback_buffer_height: 历史缓冲区高度
# line_info.mutable_area_height: 可见屏幕高度
# line_info.first_visible_line_number: 顶部可见行号
# 读取历史记录中的 100 行(从溢出点开始)
lines = await session.async_get_contents(
first_line=line_info.overflow,
number_of_lines=100
)
for line in lines:
print(line.string)
实时监控屏幕变化
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# get_screen_streamer() 返回上下文管理器
async with session.get_screen_streamer(want_contents=True) as streamer:
count = 0
while count < 10:
# 阻塞直到屏幕内容变化
contents = await streamer.async_get()
if contents:
for i in range(contents.number_of_lines):
line = contents.line(i)
if line.string.strip():
print(f" {line.string}")
count += 1
读取选中文本
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 获取当前选中的文本区域
selection = await session.async_get_selection()
# 读取选中区域的文本内容
text = await session.async_get_selection_text(selection)
print(f"选中文本: {text}")
# 设置选中区域
# 创建坐标范围
start = iterm2.Point(0, 0)
end = iterm2.Point(80, 5)
coord_range = iterm2.CoordRange(start, end)
windowed_range = iterm2.WindowedCoordRange(coord_range)
sub = iterm2.SubSelection(
windowed_range,
iterm2.SelectionMode.CHARACTER,
connected=False
)
new_selection = iterm2.Selection([sub])
await session.async_set_selection(new_selection)
6. Profile 管理
获取 Profile
async def main(connection):
app = await iterm2.async_get_app(connection)
# 获取所有 Profile
profiles = await iterm2.Profile.async_get(connection)
for p in profiles:
print(f"Profile: {p.name} (GUID: {p.guid})")
# 获取默认 Profile
default_profile = await iterm2.Profile.async_get_default(connection)
print(f"默认 Profile: {default_profile.name}")
# 获取指定 Profile(通过 GUID 列表)
specific_profiles = await iterm2.Profile.async_get(
connection,
guids=["profile-guid-here"]
)
# 获取当前会话的 Profile(含会话级覆盖)
session = app.current_window.current_tab.current_session
session_profile = await session.async_get_profile()
修改 Profile 颜色
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 获取会话的 Profile
profile = await session.async_get_profile()
# 设置背景色(RGBA,0-255)
await profile.async_set_background_color(iterm2.Color(30, 30, 30, 255))
# 设置前景色
await profile.async_set_foreground_color(iterm2.Color(220, 220, 220))
# 设置光标颜色
await profile.async_set_cursor_color(iterm2.Color(255, 255, 255))
# 设置 ANSI 颜色(0-15 对应标准 16 色)
await profile.async_set_ansi_0_color(iterm2.Color(0, 0, 0)) # 黑
await profile.async_set_ansi_1_color(iterm2.Color(205, 0, 0)) # 红
await profile.async_set_ansi_2_color(iterm2.Color(0, 205, 0)) # 绿
await profile.async_set_ansi_3_color(iterm2.Color(205, 205, 0)) # 黄
await profile.async_set_ansi_4_color(iterm2.Color(0, 0, 238)) # 蓝
await profile.async_set_ansi_5_color(iterm2.Color(205, 0, 205)) # 品红
await profile.async_set_ansi_6_color(iterm2.Color(0, 205, 205)) # 青
await profile.async_set_ansi_7_color(iterm2.Color(229, 229, 229)) # 白
# 亮色版本:8-15(ansi_8_color 到 ansi_15_color)
# 选中文本颜色
await profile.async_set_selection_color(iterm2.Color(82, 130, 183))
await profile.async_set_selected_text_color(iterm2.Color(255, 255, 255))
# 链接颜色
await profile.async_set_link_color(iterm2.Color(0, 100, 255))
# 标签颜色
await profile.async_set_tab_color(iterm2.Color(50, 100, 150))
# 徽章颜色
await profile.async_set_badge_color(iterm2.Color(255, 100, 0, 128))
使用 LocalWriteOnlyProfile 修改会话 Profile(不影响底层 Profile)
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# LocalWriteOnlyProfile 只写、不读,批量修改后一次性应用
change = iterm2.LocalWriteOnlyProfile()
# 修改背景色
change.set_background_color(iterm2.Color(20, 20, 30))
# 修改字体(格式:"FontName Size")
change.set_normal_font("MesloLGS-NF-Regular 14")
# 修改透明度(0.0=不透明,1.0=完全透明)
change.set_transparency(0.1)
# 开启背景模糊
change.set_blur(True)
change.set_blur_radius(5.0)
# 禁止 escape 序列修改标题
change.set_allow_title_setting(False)
# 应用到当前会话(只修改会话的本地副本)
await session.async_set_profile_properties(change)
修改字体
import re
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
profile = await session.async_get_profile()
# 当前字体(格式:"{FontName} {Size}")
font = profile.normal_font
print(f"当前字体: {font}")
# 增加字号(使用正则解析)
r = re.compile(r'^(.*) (\d+)(.*)$')
match = r.search(font)
if match:
name, size, rest = match.groups()
new_font = f"{name} {int(size) + 2}{rest}"
change = iterm2.LocalWriteOnlyProfile()
change.set_normal_font(new_font)
await session.async_set_profile_properties(change)
# 非 ASCII 字体
non_ascii_font = profile.non_ascii_font
change = iterm2.LocalWriteOnlyProfile()
change.set_non_ascii_font("MesloLGS-NF-Regular 14")
change.set_use_non_ascii_font(True)
await session.async_set_profile_properties(change)
透明度和模糊
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
change = iterm2.LocalWriteOnlyProfile()
# 透明度(0.0 不透明,1.0 完全透明)
change.set_transparency(0.15)
# 背景图片模糊(布尔值)
change.set_blur(True)
# 模糊半径(0-30)
change.set_blur_radius(8.0)
await session.async_set_profile_properties(change)
应用 Color Preset
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 获取可用的 Color Preset(返回 ColorPreset 对象,有 .name 属性)
preset = await iterm2.ColorPreset.async_get(connection, "Solarized Dark")
if preset:
profile = await session.async_get_profile()
await profile.async_set_color_preset(preset)
# 对所有 Profile 应用 Preset(适合主题切换)
profiles = await iterm2.PartialProfile.async_query(connection)
for partial in profiles:
full_profile = await partial.async_get_full_profile()
await full_profile.async_set_color_preset(preset)
列出可用 Color Presets
# ColorPreset.async_get() 只能通过名称获取,无法列出所有预设
# 可用内置预设名称示例:
# "Dark Background", "Light Background", "Solarized Dark", "Solarized Light"
# "Tango Dark", "Tango Light", "Tomorrow Night Eighties" 等
检测当前 Profile 使用的 Color Preset
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
profile = await session.async_get_profile()
known_presets = [
"Dark Background", "Light Background",
"Solarized Dark", "Solarized Light",
"Tango Dark", "Tango Light",
]
def colors_equal(a, b):
return (a.red == b.red and a.green == b.green and
a.blue == b.blue and a.alpha == b.alpha)
for preset_name in known_presets:
preset = await iterm2.ColorPreset.async_get(connection, preset_name)
if preset:
match = all(
colors_equal(
getattr(profile, f"ansi_{i}_color"),
getattr(preset, f"ansi_{i}_color", None) or iterm2.Color()
)
for i in range(8)
)
if match:
print(f"当前使用 preset: {preset_name}")
break
Profile 其他属性
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
profile = await session.async_get_profile()
# 滚动缓冲区
print(f"滚动行数: {profile.scrollback_lines}")
await profile.async_set_scrollback_lines(10000)
await profile.async_set_unlimited_scrollback(True)
# 光标样式
await profile.async_set_cursor_type(iterm2.CursorType.BLOCK) # 方块
# iterm2.CursorType.VERTICAL_BAR # 竖线
# iterm2.CursorType.UNDERLINE # 下划线
# 闪烁光标
await profile.async_set_blinking_cursor(True)
# 状态栏
await profile.async_set_status_bar_enabled(True)
# 铃声
await profile.async_set_silence_bell(True)
await profile.async_set_visual_bell(True)
# 关闭时不确认
await profile.async_set_prompt_before_closing(False)
await profile.async_set_close_sessions_on_end(True)
# 徽章文本
await profile.async_set_badge_text("Hello")
# 将此 Profile 设为默认
await profile.async_make_default()
7. 广播输入(Broadcast)
设置广播域
async def main(connection):
app = await iterm2.async_get_app(connection)
window = app.current_window
tab = window.current_tab
# 创建广播域(让多个 session 共享输入)
domain = iterm2.BroadcastDomain()
# 将会话加入广播域
for session in tab.sessions:
domain.add_session(session)
# 应用广播域配置(替换所有现有广播域)
await iterm2.async_set_broadcast_domains(connection, [domain])
# 清除所有广播(传入空列表)
await iterm2.async_set_broadcast_domains(connection, [])
手动广播输入到多个会话
import asyncio
import iterm2
async def main(connection):
app = await iterm2.async_get_app(connection)
# 创建四格布局
bottom_left = app.current_window.current_tab.current_session
bottom_right = await bottom_left.async_split_pane(vertical=True)
top_left = await bottom_left.async_split_pane(vertical=False, before=True)
top_right = await bottom_right.async_split_pane(vertical=False, before=True)
await bottom_left.async_activate()
broadcast_to = [top_left, bottom_left, top_right, bottom_right]
async def handle_keystroke(keystroke):
if keystroke.keycode == iterm2.Keycode.ESCAPE:
return True # 按 ESC 退出广播
for session in broadcast_to:
await session.async_send_text(keystroke.characters)
return False
pattern = iterm2.KeystrokePattern()
pattern.keycodes = list(iterm2.Keycode)
pattern.forbidden_modifiers = [iterm2.Modifier.COMMAND]
future = asyncio.Future()
async def filter_keystrokes():
async with iterm2.KeystrokeFilter(
connection, [pattern], bottom_left.session_id) as mon:
await asyncio.wait([future])
task = asyncio.create_task(filter_keystrokes())
async with iterm2.KeystrokeMonitor(
connection, bottom_left.session_id) as mon:
done = False
while not done:
keystroke = await mon.async_get()
done = await handle_keystroke(keystroke)
future.set_result(True)
await task
iterm2.run_until_complete(main)
8. 键盘监控与过滤
监控所有键盘输入
async def main(connection):
app = await iterm2.async_get_app(connection)
# 监控所有会话的键盘输入(不传 session_id)
async with iterm2.KeystrokeMonitor(connection) as mon:
for _ in range(10):
keystroke = await mon.async_get()
print(f"键: {keystroke.characters!r}")
print(f" 忽略修饰键: {keystroke.characters_ignoring_modifiers!r}")
print(f" Keycode: {keystroke.keycode}")
print(f" 修饰键: {keystroke.modifiers}")
iterm2.run_until_complete(main)
监控特定会话的键盘输入
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 监控特定会话(包含 key-up 和 flags-changed 事件)
async with iterm2.KeystrokeMonitor(
connection,
session.session_id,
advanced=True) as mon:
while True:
keystroke = await mon.async_get()
if keystroke.keycode == iterm2.Keycode.ANSI_Q:
break
iterm2.run_until_complete(main)
使用函数键切换标签
import asyncio
import iterm2
async def main(connection):
app = await iterm2.async_get_app(connection)
keycodes = [iterm2.Keycode.F1, iterm2.Keycode.F2, iterm2.Keycode.F3,
iterm2.Keycode.F4, iterm2.Keycode.F5, iterm2.Keycode.F6,
iterm2.Keycode.F7, iterm2.Keycode.F8, iterm2.Keycode.F9,
iterm2.Keycode.F10, iterm2.Keycode.F11, iterm2.Keycode.F12]
async def keystroke_handler(keystroke):
if keystroke.modifiers == [iterm2.Modifier.FUNCTION]:
try:
idx = keycodes.index(keystroke.keycode)
tabs = app.current_window.tabs
if 0 <= idx < len(tabs):
await tabs[idx].async_select()
except ValueError:
pass
pattern = iterm2.KeystrokePattern()
pattern.forbidden_modifiers.extend([
iterm2.Modifier.CONTROL, iterm2.Modifier.OPTION,
iterm2.Modifier.COMMAND, iterm2.Modifier.SHIFT, iterm2.Modifier.NUMPAD
])
pattern.required_modifiers.append(iterm2.Modifier.FUNCTION)
pattern.keycodes.extend(keycodes)
async def monitor():
async with iterm2.KeystrokeMonitor(connection) as mon:
while True:
keystroke = await mon.async_get()
await keystroke_handler(keystroke)
asyncio.create_task(monitor())
async with iterm2.KeystrokeFilter(connection, [pattern]) as f:
await iterm2.async_wait_forever()
iterm2.run_forever(main)
拦截/过滤键盘输入
async def main(connection):
# KeystrokePattern:定义要拦截的键盘模式
pattern = iterm2.KeystrokePattern()
# 拦截所有带 Ctrl 的键
pattern.required_modifiers = [iterm2.Modifier.CONTROL]
# 允许的 keycode 列表(拦截所有字母键 + Ctrl)
pattern.keycodes = [
iterm2.Keycode.ANSI_A,
iterm2.Keycode.ANSI_B,
# ...
]
# 禁止带 Command 键的组合(Command+Ctrl 不拦截)
pattern.forbidden_modifiers = [iterm2.Modifier.COMMAND]
# 在特定会话中拦截
session = (await iterm2.async_get_app(connection)).current_window.current_tab.current_session
async with iterm2.KeystrokeFilter(
connection,
[pattern],
session.session_id) as filter:
await iterm2.async_wait_forever()
iterm2.run_forever(main)
9. 焦点与布局变更监控
监控焦点变化
async def main(connection):
async with iterm2.FocusMonitor(connection) as mon:
while True:
update = await mon.async_get_next_update()
# update 的类型:
# - iterm2.FocusUpdate.ApplicationActive: iTerm2 是否激活
# - iterm2.FocusUpdate.WindowChanged: 窗口焦点变化
# - iterm2.FocusUpdate.SelectedTabChanged: 标签切换
# - iterm2.FocusUpdate.ActiveSessionChanged: 会话切换
if hasattr(update, 'window_id'):
print(f"窗口变化: {update.window_id} 原因: {update.reason}")
elif hasattr(update, 'tab_id'):
print(f"标签切换: {update.tab_id}")
elif hasattr(update, 'session_id'):
print(f"会话切换: {update.session_id}")
iterm2.run_until_complete(main)
监控布局变化
async def main(connection):
async with iterm2.LayoutChangeMonitor(connection) as mon:
while True:
await mon.async_get()
# 以下情况触发:会话移动、标签/会话排序变化、
# 大小调整、会话埋藏/恢复、窗口大小调整
app = await iterm2.async_get_app(connection)
print("布局已变更,当前结构:")
print(app.pretty_str())
iterm2.run_until_complete(main)
10. 会话生命周期监控
监控新会话创建
import asyncio
import iterm2
async def main(connection):
app = await iterm2.async_get_app(connection)
async with iterm2.NewSessionMonitor(connection) as mon:
while True:
session_id = await mon.async_get()
session = app.get_session_by_id(session_id)
if session:
print(f"新会话: {session_id}")
# 对新会话执行操作,例如设置样式
change = iterm2.LocalWriteOnlyProfile()
change.set_background_color(iterm2.Color(20, 20, 20))
await session.async_set_profile_properties(change)
iterm2.run_forever(main)
监控会话结束
async def main(connection):
async with iterm2.SessionTerminationMonitor(connection) as mon:
while True:
session_id = await mon.async_get()
print(f"会话结束: {session_id}")
iterm2.run_forever(main)
对每个会话(含未来会话)各执行一次
import asyncio
import iterm2
async def setup_session(session_id, app):
"""对每个新会话执行初始化"""
session = app.get_session_by_id(session_id)
if session:
print(f"初始化会话: {session_id}")
# 在此处理新会话...
async def main(connection):
app = await iterm2.async_get_app(connection)
# 方式一:使用 async_foreach_session_create_task(自动管理任务)
await iterm2.EachSessionOnceMonitor.async_foreach_session_create_task(
app,
lambda session_id: setup_session(session_id, app)
)
# 方式二:手动使用上下文管理器
async with iterm2.EachSessionOnceMonitor(connection) as mon:
while True:
session_id = await mon.async_get()
asyncio.create_task(setup_session(session_id, app))
iterm2.run_forever(main)
11. 变量监控
监控 Session 变量变化
import asyncio
import iterm2
async def monitor_session(connection, session_id):
async with iterm2.VariableMonitor(
connection,
iterm2.VariableScopes.SESSION,
"hostname",
session_id) as mon:
while True:
hostname = await mon.async_get()
print(f"Session {session_id} 的 hostname 变为: {hostname}")
async def main(connection):
app = await iterm2.async_get_app(connection)
for window in app.windows:
for tab in window.tabs:
for session in tab.sessions:
asyncio.create_task(
monitor_session(connection, session.session_id)
)
iterm2.run_forever(main)
监控主题变化(App 级变量)
async def main(connection):
app = await iterm2.async_get_app(connection)
# 先处理当前主题
current_theme = await app.async_get_variable("effectiveTheme")
print(f"当前主题: {current_theme}")
# 然后监控变化
async with iterm2.VariableMonitor(
connection,
iterm2.VariableScopes.APP,
"effectiveTheme",
None) as mon:
while True:
theme = await mon.async_get()
print(f"主题变为: {theme}")
# 根据主题切换配色
parts = theme.split(" ")
preset_name = "Dark Background" if "dark" in parts else "Light Background"
preset = await iterm2.ColorPreset.async_get(connection, preset_name)
if preset:
profiles = await iterm2.PartialProfile.async_query(connection)
for partial in profiles:
profile = await partial.async_get_full_profile()
await profile.async_set_color_preset(preset)
iterm2.run_forever(main)
12. Shell Prompt 监控(需要 Shell Integration)
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
# 获取最近一次 prompt 信息
prompt = await iterm2.async_get_last_prompt(connection, session.session_id)
if prompt:
print(f"命令: {prompt.command_}")
print(f"工作目录: {prompt.working_directory_}")
# 监控 Prompt 事件
async with iterm2.PromptMonitor(
connection,
session.session_id,
modes=[
iterm2.PromptMonitor.Mode.PROMPT,
iterm2.PromptMonitor.Mode.COMMAND_START,
iterm2.PromptMonitor.Mode.COMMAND_END,
]) as mon:
while True:
mode, info = await mon.async_get()
if mode == iterm2.PromptMonitor.Mode.PROMPT:
print("检测到新 prompt")
elif mode == iterm2.PromptMonitor.Mode.COMMAND_START:
print("命令开始执行")
elif mode == iterm2.PromptMonitor.Mode.COMMAND_END:
print("命令执行结束")
iterm2.run_until_complete(main)
13. 自定义控制序列(Custom Control Sequences)
注册监听器(守护进程)
#!/usr/bin/env python3
import iterm2
async def main(connection):
# 监听发往所有会话的自定义控制序列
async with iterm2.CustomControlSequenceMonitor(
connection,
"my-shared-secret", # 共享密钥(防止未授权代码调用)
r'^create-window$' # 匹配载荷的正则
) as mon:
while True:
match = await mon.async_get()
# match 是 re.Match 对象
await iterm2.Window.async_create(connection)
iterm2.run_forever(main)
监听特定会话的控制序列
async def main(connection):
app = await iterm2.async_get_app(connection)
session = app.current_window.current_tab.current_session
async with iterm2.CustomControlSequenceMonitor(
connection,
"my-secret",
r'^set-color:(.+)$', # 捕获颜色名
session_id=session.session_id
) as mon:
while True:
match = await mon.async_get()
color_name = match.group(1)
print(f"收到颜色指令: {color_name}")
iterm2.run_forever(main)
从终端发送自定义控制序列
# 在终端中发送(格式:\033]1337;Custom=id={secret}:{payload}\a)
printf "\033]1337;Custom=id=my-shared-secret:create-window\a"
printf "\033]1337;Custom=id=my-secret:set-color:blue\a"
14. RPC 注册(Remote Procedure Calls)
注册全局 RPC
import iterm2
async def main(connection):
app = await iterm2.async_get_app(connection)
@iterm2.RPC
async def clear_all_sessions():
"""清除所有会话的滚动缓冲区"""
code = b'\x1b]1337;ClearScrollback\x07'
for window in app.windows:
for tab in window.tabs:
for session in tab.sessions:
await session.async_inject(code)
# 注册(默认超时 5 秒)
await clear_all_sessions.async_register(connection)
# 自定义超时
await clear_all_sessions.async_register(connection, timeout=30.0)
iterm2.run_forever(main)
带 Session 上下文的 RPC
async def main(connection):
app = await iterm2.async_get_app(connection)
@iterm2.RPC
async def rename_session(
session_id=iterm2.Reference("id"), # 必选:当前 session ID
new_name=iterm2.Reference("user.newName?") # 可选:用户变量(? 表示可选)
):
session = app.get_session_by_id(session_id)
if session:
name = new_name or "Unnamed"
await session.async_set_name(name)
await rename_session.async_register(connection)
iterm2.run_forever(main)
RPC 绑定快捷键
在 iTerm2 中:Preferences > Keys > Key Bindings > + > Action: Invoke Script Function,填入函数调用字符串,例如:
clear_all_sessions()
rename_session(new_name: "MySession")
15. 自定义标题提供者(Title Provider)
import iterm2
async def main(connection):
@iterm2.TitleProviderRPC
async def custom_title(
auto_name=iterm2.Reference("autoName?"),
job=iterm2.Reference("jobName?"),
hostname=iterm2.Reference("hostname?")
):
parts = []
if hostname:
parts.append(hostname)
if job:
parts.append(job)
if auto_name:
parts.append(auto_name)
return " | ".join(parts) if parts else ""
await custom_title.async_register(
connection,
display_name="Host | Job | Auto", # 在偏好设置中显示的名称
unique_identifier="com.example.custom-title" # 反向域名格式的唯一 ID
)
iterm2.run_forever(main)
16. 自定义状态栏组件(Status Bar)
import iterm2
import asyncio
async def main(connection):
component = iterm2.StatusBarComponent(
short_description="Git Branch",
detailed_description="显示当前 Git 分支名称",
knobs=[
iterm2.CheckboxKnob("显示图标", True, "show_icon"),
iterm2.StringKnob("前缀", "branch: ", "", "prefix"),
],
exemplar="branch: main",
update_cadence=5, # 每 5 秒刷新,None 表示手动触发
identifier="com.example.git-branch"
)
@iterm2.StatusBarRPC
async def git_branch_coro(
knobs,
path=iterm2.Reference("path?")
):
if not path:
return ""
try:
import subprocess
result = subprocess.run(
["git", "branch", "--show-current"],
capture_output=True, text=True, cwd=path
)
branch = result.stdout.strip()
prefix = knobs.get("prefix", "branch: ")
return f"{prefix}{branch}" if branch else ""
except Exception:
return ""
await component.async_register(connection, git_branch_coro)
iterm2.run_forever(main)
17. 自定义右键菜单项(Context Menu)
import iterm2
async def main(connection):
@iterm2.ContextMenuProviderRPC
async def open_in_editor():
"""在编辑器中打
…(truncated)