资讯动态

kitty Custom Kittens 开发实战:用 Python 为 GPU 终端编写可复用的交互扩展

发布时间:2026/9/10 21:27:32 来源:尧图企业网站定制
kitty Custom Kittens 开发实战用 Python 为 GPU 终端编写可复用的交互扩展【免费下载链接】kittyIf you live in the terminal, kitty is made for you! Cross-platform, fast, feature-rich, GPU based.项目地址: https://gitcode.com/GitHub_Trending/ki/kitty导读kitty 提供了一套名为Kitten的扩展机制任何你熟悉的终端程序式 Python 脚本都可以一键挂载到 kitty 中运行并反过来操控正在运行的 kitty 实例——读取当前窗口内容、模拟按键与鼠标、切换布局、粘贴文本、发送远程控制命令。本文以官方文档 docs/kittens/custom.rst 为骨架结合 kittens/runner.py、kittens/tui/handler.py、kitty/boss.py 与 kitty/mouse.c 等源码完整讲解自定义 Kitten 的执行模型、两段式函数接口、屏幕输入类型矩阵、无界面脚本 Kitten、鼠标事件模拟与远程控制集成等全套能力。读完你就能从零写出自己的 Kitten 并绑定到快捷键上使用。Kitten 的运行模型一个跑在 overlay 窗口里的普通终端程序官方对 Kitten 的定义非常精炼They are just terminal programs written in Python——Kitten 本质上就是一个 Python 终端程序。当你在 kitty 中启动一个 Kitten 时kitty 会在当前窗口之上打开一个overlay覆盖层窗口来运行这个程序对用户表现为一个临时弹出的子界面可选地把当前窗口/滚动回滚scrollback的内容通过STDIN交给 Kitten 进程读取Kitten 程序在 overlay 窗口里照常读写、绘制文本、响应按键像任何普通终端程序一样工作Kitten 程序结束后kitty 进程会调用其handle_result()回调把 Kitten 运行期间的返回值连同运行上下文一并传入此时你的代码持有boss对象——即正在运行的 kitty 实例的入口可以执行关闭窗口、粘贴文本、切换布局等任意操作。程序本体与结果处理被刻意拆成了两个阶段、两个进程环境这是理解 Kitten 一切 API 设计的关键。源码层面这套流程在 kittens/runner.py 中有完整实现launch()kittens/runner.py负责真正运行 Kitten 的main()它先把KITTY_CONFIG_DIRECTORY写入环境变量再调用mstart若main()返回了非None结果会把结果用jsonbase64.b85encode编码后以 kitty 私有 DCS 序列\x1bPkitty-kitten-result|...\x1b\\写回 stdout——这就是返回值能跨进程传递回 kitty 主进程的通信协议。import_kitten_main_module()kittens/runner.py负责加载 Kitten路径以.py结尾的被当作自定义 Kitten直接读取源码文件执行否则去内置的kittens.name.main模块中寻找。create_kitten_handler()kittens/runner.py读取main()/handle_result()上附加的各种注解属性如type_of_input、no_ui、allow_remote_control并把handle_result与[kitten名] 原始参数绑定成一个可供回调的对象。第一个自定义 Kitten获取输入并粘贴到窗口文档给出的入门示例是一个向用户提问、再把答案粘贴回终端的 Kitten。在你的 kitty 配置目录Linux 上通常为~/.config/kitty具体位置参见 docs/conf.rst下新建mykitten.pyfrom kitty.boss import Boss def main(args: list[str]) - str: # 这是 Kitten 的入口运行在 overlay 窗口中 answer input(Enter some text: ) # main() 的返回值会原样传给 handle_result() 的 answer 参数 return answer def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) - None: # 找到发起本次 Kitten 的目标窗口 w boss.window_id_map.get(target_window_id) if w is not None: w.paste_text(answer)然后在kitty.conf中绑定快捷键map ctrlk kitten mykitten.py重启或让 kitty 重新加载配置后按CtrlKoverlay 窗口出现并提示输入输入内容回车后会被原样粘贴回被覆盖的下层窗口。两点值得注意的机制细节main()运行在独立子进程的 overlay 终端里因此可以直接使用input()、print()做交互handle_result()则运行在kitty 主进程内直接面对boss。target_window_id就是在哪个窗口里按下了快捷键用它去boss.window_id_map见 kitty/boss.py一张以窗口 ID 为键的弱引用字典取回Window对象再调用 kitty/window.py 中定义的w.paste_text(text)即可完成回写。文档特别指出所有内部对象访问都建议做is not None之类的存在性检查——毕竟目标窗口可能在 Kitten 运行期间已被关闭。最推荐的入门方式正如文档所说直接修改内置 Kitten。它们全部位于源码仓库的 kittens/ 目录例如 kittens/ask/、kittens/choose_files/、kittens/hints/从真实实现入手比从空文件开始要快得多。用 Remote Control API 操控 kitty更稳的推荐路径Kitten 代码虽然拥有完整的内部 kitty API 访问权但文档明确指出这些内部 API既不稳定也没有文档。因此推荐的可靠做法是调用 kitty 官方的 Remote control API也就是命令行kitten 背后那套接口。在handle_result()里只需一行包装调用def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) - None: # 找到本次 Kitten 的目标窗口 w boss.window_id_map.get(target_window_id) if w is not None: boss.call_remote_control(w, (send-text, f--matchid:{w.id}, hello world))boss.call_remote_control()的入参与你在终端里执行kitten send-text ...的参数一一对应kitty/boss.py 内部会复用同一套parse_rc_args/parse_subcommand_cli解析管线。重要提示在handle_result()执行期间boss.active_window仍然指向运行该 Kitten 的那个窗口overlay 所在窗口而不是你希望操作的目标窗口。因此凡是通过远程控制定位目标窗口的命令都必须显式给出选择参数——最常见的就是上例中的--matchid:{w.id}或者--self。在 kitty 终端里执行kitten --help可以列出全部可用的远程控制命令发送文本、新建窗口/标签页、切换布局、调整字体大小、截屏等均可从 Kitten 内触发相关命令语义见 docs/remote-control.rst。向 Kitten 传参固定参数与 selectionKitten 支持在键位映射里直接携带静态参数map ctrlk kitten mykitten.py arg1 arg2这些参数会以列表形式出现在main(args)与handle_result(args)的args形参中。与之配套还有两个自动展开的约定当前工作目录Kitten 启动时其cwd 被设置为活动窗口中正在运行程序的工作目录实现在run_kitten_with_metadata()中取CwdRequest(w).cwd_of_child见 kitty/boss.py。这让 Kitten 天然跟随用户当前所在目录。特殊参数selection会被自动替换为活动窗口中当前选中的文本。若没有选中内容替换为空。实现位于 kitty/boss.pyrun_kitten_with_metadata()在真正启动前会遍历args凡命中selection就用data_for_at(whichselection, windoww)的结果顶替。这意味着你可以写map ctrlk kitten mykitten.py selection从而让 Kitten 直接处理用户选中的文本——这是选中即处理类工具翻译、格式化、搜索最常用的接入点。读取屏幕内容type_of_input 输入类型矩阵很多实用 Kitten 需要拿到当前窗口里有什么。做法是在handle_result()上打一个result_handler(type_of_input...)注解kitty 就会按指定类型把屏幕内容经STDIN喂给 Kitten 进程。注意两种环境下的 STDIN 含义完全不同注释中特意强调from kitty.boss import Boss # 在 main 中STDIN 属于 Kitten 进程里面装着屏幕内容 def main(args: list[str]) - str: return sys.stdin.read() # 在 handle_result 中STDIN 属于 kitty 进程本身Kitten 不应去读它 from kittens.tui.handler import result_handler result_handler(type_of_inputtext) def handle_result(args: list[str], stdin_data: str, target_window_id: int, boss: Boss) - None: pass注解被 kittens/tui/handler.py 的result_handler()读取并封装成HandleResult对象runner.py随后把type_of_input暴露给启动流程kittens/runner.pykitty 便据此准备 STDIN 数据。文档给出的type_of_input取值共 13 种按来源 × 格式两维组合关键字STDIN 中收到的内容text活动窗口的纯文本ansi活动窗口的带格式文本含 ANSI 样式序列screen活动窗口的纯文本含换行标记screen-ansi活动窗口的带格式文本含换行标记history活动窗口及其回滚scrollback的纯文本ansi-history活动窗口及其回滚的带格式文本screen-history活动窗口及其回滚的纯文本含换行标记screen-ansi-history活动窗口及其回滚的带格式文本含换行标记output上一条已运行命令的输出纯文本output-screen上一条已运行命令的输出纯文本含换行标记output-ansi上一条已运行命令的输出的带格式文本output-screen-ansi上一条已运行命令的输出的带格式文本含换行标记selection当前用鼠标选中的文本几点补充语义screen系列里的换行标记用于区分终端里因宽度导致的软换行与真正的回车换行供需要精确重建行结构的程序使用。除output最近一次运行命令的输出之外还有两个变体last_visited_output最近一次被跳转到的命令的输出和first_output当前屏幕上第一条命令的输出它们同样可与screen/ansi组合出带格式/带换行标记的版本。所有基于命令输出的类型都依赖 Shell integration即 kitty 注入 shell 的提示符/命令标记未启用 shell integration 时这些类型不可用——文档为此单独加了 warning。背后的取数实现统一收敛在boss.data_for_at(which, window, add_wrap_markers)与模块级函数data_for_at()见 kitty/boss.py 与 kitty/boss.pyrun_kitten_with_metadata()在创建 overlay 前会依据传入的type_of_input用add_wrap_markers stdin.endswith(_wrap)这类约定决定是否追加换行标记再取回文本作为 overlay 子进程的stdin数据kitty/boss.py。脚本化 kittyno_uiTrue跳过终端界面直接干活如果你只想让 Kitten 脚本化地操控 kitty、根本不需要任何交互界面可以在handle_result()上加result_handler(no_uiTrue)并让main()留空。这样 kitty不会先运行main()与 overlay 界面而是直接调用handle_result()——等同于一键执行一段带完整boss上下文的 Python 脚本。文档给出的经典案例是一个等价于内置toggle_layout动作的缩放zoom切换器。在配置目录下新建zoom_toggle.pyfrom kitty.boss import Boss def main(args: list[str]) - str: pass from kittens.tui.handler import result_handler result_handler(no_uiTrue) def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) - None: tab boss.active_tab if tab is not None: if tab.current_layout.name stack: tab.last_used_layout() else: tab.goto_layout(stack)再绑定map f11 kitten zoom_toggle.py此后按F11即在stack 布局当前窗口最大化与之前的布局之间来回切换。last_used_layout()与goto_layout()是 kitty/tabs.py 与 kitty/tabs.py 中定义的标准标签页布局动作no_ui分支的快速通道实现在run_kitten_with_metadata()里kitty/boss.py检测到end_kitten.no_ui后直接同步调用handle_result并返回全程不建窗口。想玩得更花哨在handle_result()里加一行boss.toggle_fullscreen()kitty/boss.py 的实现对应 Toggle the fullscreen status of the active OS Window 动作就能让F11同时完成布局缩放 全屏def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) - None: tab boss.active_tab if tab is not None: if tab.current_layout.name stack: tab.last_used_layout() else: tab.goto_layout(stack) boss.toggle_fullscreen()这种无 UI Kitten正是把任意 kitty 动作串成自定义复合快捷键的推荐姿势不用改一行 C/Go 内核代码就能自由组合布局、窗口、标签页等高层动作。模拟鼠标事件send_mouse_event当窗口内运行的程序开启了鼠标事件接收如tmux、vim的鼠标模式、各类 TUI你的 Kitten 可以调用底层接口把合成的鼠标事件注入给该程序。函数签名如下from kitty.fast_data_types import send_mouse_event send_mouse_event(screen, x, y, button, action, mods)参数语义screen目标窗口的screen属性例如boss.active_window.screen。x、y从 0 开始计数的单元格坐标。button沿用 X11 的按钮编号体系——左键1、中键2、右键3、滚轮上4、滚轮下5、滚轮左6、滚轮右7、后退键8、前进键9。actionPRESS、RELEASE、DRAG或MOVE四者之一。mods修饰键位掩码GLFW_MOD_{mod}{mod}取SHIFT、CONTROL、ALT可组合例如GLFW_MOD_SHIFT | GLFW_MOD_CONTROL。以上常量全部从kitty.fast_data_types导入。例如向活动窗口的 (x2, y3) 位置发送一次左键按下from kitty.fast_data_types import send_mouse_event, PRESS send_mouse_event(boss.active_window.screen, 2, 3, 1, PRESS, 0)关键行为官方文档与源码双重印证只有当目标程序正在接收此类鼠标事件时事件才会真的被发出。底层 C 实现位于 kitty/mouse.c先检查screen-modes.mouse_tracking_mode与当前动作是否匹配ANY_MODE全收MOTION_MODE只收非MOVEBUTTON_MODE只收PRESS/RELEASE匹配后才经encode_mouse_event_impl()编码成标准 CSI 鼠标转义序列通过write_escape_code_to_child()写入子进程。因此函数返回True表示事件已发送、False表示程序未开启对应鼠标模式事件被静默丢弃。在 main() 内使用远程控制kitten_ui(allow_remote_controlTrue)通常远程控制要等handle_result()阶段、Kitten 退出后才可执行。但若你希望在 Kitten展示交互 UI 之前先探查 kitty 状态或者希望用户在 Kitten 界面上就能直接操控 kitty可以在main()上启用kitten_ui的allow_remote_controlTrue——它告诉 kitty即使全局未开启远程控制也要以允许远程控制的方式运行本 Kittenimport json import sys from pprint import pprint from kittens.tui.handler import kitten_ui kitten_ui(allow_remote_controlTrue) def main(args: list[str]) - str: # 取得运行 kitten ls 的输出 cp main.remote_control([ls], capture_outputTrue) if cp.returncode ! 0: sys.stderr.buffer.write(cp.stderr) raise SystemExit(cp.returncode) output json.loads(cp.stdout) pprint(output) # 打开一个标题由用户指定的新标签页 title input(Enter the name of tab: ) window_id main.remote_control([launch, --typetab, --tab-title, title], checkTrue, capture_outputTrue).stdout.decode() return window_id几点机制解释main.remote_control(cmd, **kw)是对 Pythonsubprocess.run的薄封装实现于 kittens/tui/handler.py内部会先拼出kitten 前缀再执行你给出的子命令。默认出于安全考虑Kitten 派生的子进程不能使用远程控制——这就是必须经由main.remote_control()的原因它通过显式传递 fd 让子命令拿到权限。若你的设计确实需要让 Kitten 的子进程也获得远程控制能力可调用main.allow_indiscriminate_remote_control()放开限制其实现见 kittens/tui/handler.py本质是把远程控制 socket fd 设为可继承必要时还会通过KITTY_RC_PASSWORD环境变量传递口令。注意main变量此处指代的是被装饰的函数对象本身kitten_ui返回的是KittenUI实例kittens/tui/handler.py因此main.remote_control、main.password、main.allow_indiscriminate_remote_control等都是这个装饰器实例提供的方法/属性。远程控制访问还可以进一步收紧到白名单在装饰器中指定remote_control_password参数kitty 会为本次 Kitten 会话生成一个安全随机口令并只允许列出的命令例如kitten_ui(allow_remote_controlTrue, remote_control_passwordls set-colors) def main(args: list[str]) - str: ...其中remote_control_password的值是一个空格分隔的允许命令列表完整语义参见配置项remote_control_password说明见 docs/conf.rst。生成的密码可通过main.password读取main.remote_control()会自动携带它完成鉴权无需手工处理。KittenUI.initialize()kittens/tui/handler.py里展示了口令下发流程kitty 通过 socketpair 一端把密码先行送入Kitten 侧从rc_fd读取并在后续子命令中通过--password-file fd:N --use-password always提交。调试 Kittenprint 去了哪里因为main()和handle_result()运行在两个不同的进程环境调试输出也需要分情况讨论main()是普通程序print()的输出直接显示在 Kitten 的 overlay 窗口里肉眼可见。若想把这些输出留到 kitty 主进程的 stdout可以用专用工具from kittens.tui.loop import debug debug(whatever)debug()用法与print()一致但输出会进入运行该 Kitten 的 kitty 进程的 STDOUT。runner.py的set_debug()kittens/runner.py甚至把debug直接注入到 Python 内置命名空间方便在任意位置直接调用。handle_result()运行在 kitty 主进程内部其print()输出自然进入kitty 进程自身的 STDOUT。文档给出一条实用技巧从另一个 kitty 实例中运行被测的 kitty那么handle_result()的 print 就会显示在外层那个 kitty 的窗口里实现跨实例观察调试日志。若在 overlay 阶段抛出了未处理异常runner.py的main()兜底逻辑kittens/runner.py会打印 traceback 并提示Press Enter to quit避免窗口瞬间消失丢失报错。结语从内置 Kitten 出发打造随 kitty 一起分发的 Go 版 Kitten自定义 Python Kitten 适合放进~/.config/kitty/个人使用如果希望你的扩展成为内置 Kitten即直接以kitten my-kitten调用、随 kitty 一起分发则要走 Go 语言开发路线——内置 Kitten 由 Go 主体加薄薄一层 Python CLI 包装组成完整的新建步骤、main.py/main.go模板与tools/cmd/tool/main.go的注册方法见 developing-builtin-kittens。社区里也已沉淀出大量用户自建 Kitten 可供参考例如在 vim 与 kitty 分屏之间用统一热键无缝跳转的导航器、让 kitty 滚动键在全屏应用中生效的 smart-scroll、带预览的标签页模糊切换器、把自然语言提示词交给 LLM 生成 shell 命令的 gattino、只在密码提示符处安全插入密码管理器口令的 Kitten、面向 WeeChat 的 URL 提示 Kitten以及把 kitty 输出经 weasyprint 导出 PDF 的工具与右键弹出操作菜单的 action menu完整清单收录于 docs/kittens/custom.rst 末节。掌握了两段式函数契约、输入类型矩阵、no_ui脚本通道与远程控制接入方式之后把任意想对终端内容做的批量处理包装成一次快捷键调用就只是几十行 Python 的事了。【免费下载链接】kittyIf you live in the terminal, kitty is made for you! Cross-platform, fast, feature-rich, GPU based.项目地址: https://gitcode.com/GitHub_Trending/ki/kitty创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价