Files
s0xdk--ghostty-blackhole/README.md
T
2026-07-13 10:23:20 +00:00

18 KiB
Raw Blame History

Note

本文档由 WeHub 基于上游 README 翻译整理,属于社区翻译,非官方中文文档。
English · 原始项目 · 上游 README
原作者、版权与许可证归属以原始项目及本仓库 LICENSE 文件为准。

Ghostty Blackhole

Ghostty Blackhole demo

Landing page: s13k.dev/blackhole

一个漂浮在你 Ghostty 终端里的黑洞。它从小开始生长,最终吞噬整个屏幕,具体行为取决于你选择的 尺寸模式(size mode:内置的 番茄钟(pomodoro 时钟(在一小时内逐渐变大、提醒你休息、休息后就不再打扰),或 token 模式(token mode,实时追踪 Claude Code 上下文窗口(context window 的填充程度。

本效果基于 Eric Bruneton 的黑洞着色器,,该着色器通过光束追踪(beam-trace)史瓦西(Schwarzschild)测地线,并对预计算的查找表进行采样。Ghostty 自定义着色器是单遍 Shadertoy 风格的片段着色器,没有自定义纹理,因此查找表被替换为实时物理计算:黑洞附近每个像素都会沿史瓦西度规(Schwarzschild metric积分各自的零测地线(null geodesicBinet 形式的加速度 a = -3/2 h² x/r⁵,可给出精确的光子偏折)。你的终端内容充当透镜背景天空的角色。下方没有任何东西是画上去的——一切都来自光线追踪:

渲染内容

  • 阴影(shadow — 碰撞参数低于 b_crit = (3√3/2) r_s 的光线螺旋穿过视界后返回纯黑。边缘附近的文字在消失前会被拉伸进光子环(photon ring);黑洞后方的文字确实消失了。
  • 引力透镜(gravitational lensing — 逃逸光线被投影回终端的「天空」平面:文字弯曲、放大,并在爱因斯坦环(Einstein ring)内呈现镜像的次级像。远离黑洞处会交接给解析弱场偏折(α = 2r_s/b),因此只有黑洞附近的像素需要承担积分开销。远处蓝光比红光偏折略多,带来一丝色差(chromatic aberration)。
  • 吸积盘(accretion disk — 薄开普勒盘(Keplerian disk),光线可多次穿透:远侧在阴影上下方形成弧线(星际穿越 风格),光子环区域显示更高阶的盘面像。着色遵循物理:Shakura–Sunyaev 温度分布以黑体色渲染,再经相对论因子 g = √(1 1.5 r_s/r)/(1 β·k̂) 红移与束流化(beaming)—— 接近侧蓝热,并由 g^N 增强;远离侧暗淡偏红。
  • 光子环(photon ring — 在 1.5 r_s 光子球(photon sphere)附近缠绕的光线会拾取每一次盘面穿越;明亮细环是涌现效果,并非直接绘制。
  • 透镜星场(lensed starfield — 以 弯曲 后的光线方向采样的微弱程序化天空,恒星在黑洞周围拖成弧线(默认关闭 — 提高 STAR_GAIN 即可启用)。
  • 引力时间膨胀(gravitational time dilation — 半径 r 处的盘面图案以固有时速率 √(1 1.5 r_s/r) 前进,内轨道可见地「冻结」;随着黑洞变重(DILATION_MIN),整个盘面也会减速。
  • 黑洞沿缓慢的利萨如(Lissajous)路径漂移,限制在屏幕上半部分 — 底部 WORK_AREA 比例区域(你的提示符)永远不会被扭曲。漂移速度与活动范围随尺寸变化:小时平静,大时躁动。

番茄钟模式

休息提醒完全在着色器内计算 — 无需守护进程、shell 钩子,blackhole.glsl 之外什么都不需要。

着色器是无状态的(帧间不保留缓冲区,且 Ghostty 没有自定义 uniform),因此着色器无法记住 你的 工作时段何时开始。取而代之的是,时间表通过 iDate 锚定到挂钟时间:

  • 周期(cycle:工作时黑洞始终存在 — 在周期下限处从小开始,在 WORK_PERIOD_MIN(默认 55 分钟)内逐渐变大,最后一分钟缩回小尺寸,并在 BREAK_MIN(默认 5 分钟)内保持小尺寸。55+5 时,峰值落在每小时差 5 分 — 固定、可预测的节奏。
  • 输入检测器(typing detectoriTimeCursorChange 追踪终端中的光标活动。停止使用 IDLE_FADE_SEC(默认 90 秒)后,黑洞会实时缩小,安静几分钟后完全消失 — 你实际没在工作时它不会烦你。

自包含的代价是:周期不会在你于非常规时间休息时重新锚定 — 它是每小时一次的钟声,而非按工作 streak 计时的秒表。

注意: 原版 Ghostty(截至 1.3)声明了 iDate 但从未填充 — 它始终为零 — 因此在当前版本中挂钟时间表不会推进:黑洞停留在小尺寸周期下限,只有输入检测器有效。一旦 Ghostty 接入 iDate,完整周期就会生效(今天可用 TIME_SCALE 预览,它改用 iTime 运行)。Token 模式(默认)不受影响。

尺寸模式

驱动黑洞的因素由 blackhole.glsl 顶部附近的 SIZE_MODE 选择:

  • MODE_POMODORO — 上文所述的自包含挂钟时间表。可独立工作,除着色器外无需配置(但请参阅上文 iDate 的注意项)。
  • MODE_TOKENS (默认) — 黑洞追踪 Claude Code 上下文窗口填充度。需要配套命令(见下文)。
  • MODE_DEMO — 自运行的 42 秒展示循环,用于录制演示:黑洞从角落种子长到 100%,与 token 模式完全一致,同时盘面外观依次浏览调谐预设(Inferno → Gargantua → M87* 甜甜圈 → Face-on ember → Quasar → Blazar → Pure lens → Inferno),在每个约 5 秒的时段边界交叉淡入淡出。一切都在单个编译着色器内由 iTime 驱动 — 无需重载,录制不会卡顿。用 ./demo-mode.sh on|off 切换(也会重载 Ghostty);演示模式下忽略光标通道,因此实时 Claude 会话不会干扰录制。可录制任意完整周期 — 循环重启很明显(黑洞会弹回角落种子)。

Token 模式

黑洞实时反映 Claude 上下文窗口的填充程度:

  • 空上下文右上角 一个小黑洞,大小覆盖终端面积的 TOKEN_AREA_MIN(默认 0.06 %)— 任意窗口形状下体感大小一致。
  • 逐渐填满 — 向 TOKEN_AREA_MAX 生长(100 % 上下文时默认约为终端的 ~3 % — 那是 阴影;明亮盘面可延伸到约 3 倍之外,因此视觉上更大),移动 更快,允许漫游区域从角落向左、向下扩展,直到覆盖工作区上方整个可玩屏幕;黑洞在其中伪随机游荡。轨道按区域缩放(永不裁剪 — 裁剪会把它钉死在边界),边距确保阴影和明亮内盘在较小时仍留在屏幕上。
  • /compact 或新会话 — 弹回角落种子。
  • 未运行 Claude 会话 — 黑洞完全消失;你得到普通终端。

工作原理

Ghostty 自定义着色器不接受自定义 uniform — 但 获得光标颜色(iCurrentCursorColor),任何程序都可用标准 OSC 12 转义序列设置光标颜色。因此 token 计数通过光标传入:单个配套脚本 claude-token.py 以三种方式接入 Claude Code,将上下文填充编码进琥珀色光标颜色的低 4 位(#f5b000 空 → #f0bf0a 满);着色器每帧解码。固定高 4 位加上 4 位校验和构成 16 位签名,因此主题自带的光标颜色不会意外召唤黑洞。不重写文件、不重载、无重编译卡顿 — 更新在下一帧生效。

级别阶梯也会平滑过渡:Ghostty 在任何光标变化(包括颜色)时递增 iTimeCursorChange,并将旧颜色快照到 iPreviousCursorColor,着色器解码两者并在两级之间平滑过渡 — 离散更新呈现为连续运动,而非整个扭曲场突然弹出。平滑时间随跳变幅度缩放(TOKEN_GLIDE_*):1 % 步进在 0.3 秒内缓动,10 % 跳变需 1 秒,上限 1.5 秒。

接线 触发时机 作用
statusLine 每次 assistant 回合 将上下文填充(0..1,1/250 步进)编码进光标颜色,并打印内置风格行:⚫️ ██████░░░░ 61% · Fable 5 · Projects/blackhole · ⎇ main · $1.27 · 5h 24% · wk 41%(最后一段是你的 Claude 用量限制;窗口超过 80 % 时还会显示重置时间)
SessionStart hook 会话开始 / 恢复 / /clear 重置为角落种子(0.0
SessionEnd hook 会话退出(/exitctrl-d、…) 重置光标颜色(OSC 112)— 无签名即无黑洞

没有签名特征的光标颜色表示「无会话」,因此全新安装后不会显示任何效果,直到活跃会话编码出真实的填充值。(着色器中的 TOKEN_LEVEL 定义仍可作为手动覆盖,用于手动测试尺寸;它仅在光标未携带信号时生效。)

安装 token 模式

需要两部分——着色器和命令:

  1. 将 Ghostty 指向该着色器(见下方 安装),使用 SIZE_MODE MODE_TOKENS(默认值)。

  2. 将以下内容添加到 ~/.claude/settings.json(请调整路径),然后启动新的 Claude Code 会话:

    {
      "statusLine": {
        "type": "command",
        "command": "/path/to/blackhole_ghostty/claude-token.py"
      },
      "hooks": {
        "SessionStart": [{ "hooks": [{ "type": "command", "command": "/path/to/blackhole_ghostty/claude-token.py" }] }],
        "SessionEnd":   [{ "hooks": [{ "type": "command", "command": "/path/to/blackhole_ghostty/claude-token.py" }] }]
      }
    }
    

这是全局配置,因此黑洞会对任意 Claude Code 会话做出反应。几点说明:

  • 光标颜色是按 surface 维护的状态,因此每个 Ghostty 分屏/窗口都有各自的黑洞——不同 surface 中的并发会话不会互相干扰。
  • 会话活跃时,光标会变为吸积盘琥珀色(那就是数据通道)。其他任何会重着色光标的设置都会在下次 statusline 刷新时被覆盖;没有签名特征的光标 simply 表示「无黑洞」。
  • 在 tmux/screen 内,OSC 需要透传才能到达 Ghostty——token 模式面向纯 Ghostty 会话设计。
  • 若要退出,请设置 SIZE_MODE MODE_POMODORO 并移除上述条目。

安装

需要 Ghostty 1.3+(用于光标着色器 uniform)。

克隆仓库,然后添加到你的 Ghostty 配置(macOS 上使用 ~/.config/ghostty/config~/Library/Application Support/com.mitchellh.ghostty/config):

custom-shader = /path/to/blackhole_ghostty/blackhole.glsl
custom-shader-animation = true

重新加载配置(macOS 上使用 cmd+shift+,)或打开新窗口。

调参

调参应用(macOS

一款原生 SwiftUI 控制面板,风格类似 Bruneton's demo page 位于 tuner/:为每个着色器可调参数提供分组滑块、预设(GargantuaQuasarM87* donutBlazarInfernoZen 等),与文件双向实时同步,且每次微调都会通过 SIGUSR2 立即热重载 Ghostty。

预设——Defaults、Gargantua、Quasar、Face-on ember、M87* donut、Blazar、Inferno、Pure lens、Zen

cd tuner && swift run            # run it
./tuner/make-app.sh              # or bundle tuner/dist/Black Hole Tuner.app

或直接编辑 blackhole.glsl 顶部的常量并重新加载(cmd+shift+,)。

常量

位于 blackhole.glsl 顶部。半径以史瓦西半径(r_s)为单位;ISCO(最内稳定圆轨道,innermost stable circular orbit)位于 3 r_s

Constant Effect
HOLE_RADIUS 尺寸旋钮——番茄钟:满尺寸时的阴影半径(占屏幕高度的比例);token 模式:缩放面积校准(在 0.08 时精确)
LENS_DEPTH 黑洞到终端「天空」平面的距离,单位为 r_s——数值越大,文字弯曲越明显
STAR_GAIN 透镜化星场的亮度(0 = 关闭)
DISK_INNER / DISK_OUTER 吸积盘内/外缘,单位为 r_s(内缘会钳制在光子球之外)
DISK_INCL 吸积盘倾角,弧度:0 为正面朝向,π/2 为边缘朝向
DISK_ROLL 整个系统在屏幕平面内的旋转
DISK_GAIN 吸积盘发射亮度
DISK_OPACITY 近侧吸积盘遮挡后方内容的程度
DISK_TEMP 最热环带的黑体温度,开尔文
DOPPLER_MIX 相对论性颜色/亮度不对称:0 关闭,1 全开
DISK_BEAM 束流指数——强度按 g^N 缩放
DISK_SPEED 条纹图案速度;负值反转轨道方向
DISK_WIND 条纹螺旋缠绕紧密度
DISK_CONTRAST 条纹对比度:0 = 柔和雾状,更高 = 锐利丝状结构
EXPOSURE 吸积盘光的色调映射曝光(文字从不做色调映射)
DRIFT_SPEED 黑洞漂浮移动的速度
WORK_AREA 屏幕底部保持完全无畸变的比例
DILATION_MIN 黑洞完全展开时吸积盘图案的时间速率(更低 = 减速更明显)
TOKEN_AREA_MIN token 模式:0% context 时的阴影面积,占终端面积的比例(默认 0.06%)
TOKEN_AREA_MAX token 模式:100% context 时的阴影面积(默认约为终端的 ~3%——渲染成本随之缩放)
TOKEN_HOME_X / TOKEN_HOME_Y token 模式:角落归位位置,uv 坐标(1,0 = 精确右上角;y 轴自上而下)
TOKEN_EASE token 模式:增长曲线指数——1 = 按比例,<1 前置增长,>1 直到后期才保持较小
TOKEN_REACH token 模式:100% context 时漫游框覆盖的可玩屏幕比例
TOKEN_CALM / TOKEN_RUSH token 模式:0% / 100% context 时的漂移速度
WORK_PERIOD_MIN 每个番茄钟周期的工作分钟数(增长阶段)
BREAK_MIN 每个周期的休息分钟数(黑洞保持较小)
IDLE_FADE_SEC 停止输入后黑洞开始淡出的时间
TIME_SCALE 仅用于测试:1 = 真实日程;>1 通过 iTime 快进增长

N_STEPS(一个 #define)设置每像素的测地线积分预算;只有黑洞周围光线追踪圆内的像素才需要支付这一成本。它是主要的性能旋钮——该圆随黑洞缩放,因此大屏幕高 DPI 上的大黑洞正是帧率崩溃之处。若终端在高 context 填充时变慢,请降低 N_STEPS(和/或 TOKEN_AREA_MAX)。

若要在没有 Claude 会话的情况下目测任意 token 级别,可在 Ghostty 内的普通 shell 中手动驱动光标颜色通道:./token-test.sh 0.42 保持某一级别,./token-test.sh sweep 在 25 秒内从 0 → 100% 运行,./token-test.sh off 再次隐藏黑洞。(不要在有活跃会话的 surface 中运行——其 statusline 每次刷新都会重新发出真实级别。)请注意,尺寸平滑过渡仅在光标保持静止时才能持续——任何光标移动都会让 Ghostty 将 previous 快照为 current,从而提前结束过渡——这也是 hold 模式在退出回到提示符前会停留约 1.6 秒的原因。

若要快速调试循环,将 TIME_SCALE 设为例如 100,即可在约 36 秒内观看完整的番茄钟周期——增长、坍缩、休息——然后将其设回 1。(它通过 iTime 快进,而非挂钟,因此在 iDate 卡在零的构建上也能工作。)周期旋钮也接受小数分钟,若你更希望缩短真实日程本身。

Ghostty 提供给自定义着色器的 Uniform(1.3)

iResolutioniTimeiTimeDeltaiFrameRateiFrameiMouse(未使用)、iDate(挂钟——已声明但在 Ghostty 1.3 之前一直卡在零,见番茄钟注意事项)、iChannel0(终端,iChannel1-3 未使用)、iCurrentCursor/iPreviousCursorxy 位置,zw 尺寸)、iCurrentCursorColor/iPreviousCursorColoriCurrentCursorStyle/iPreviousCursorStyleiCursorVisibleiTimeCursorChangeiFocusiTimeFocusiPalette[256]iBackgroundColoriForegroundColoriCursorColoriCursorTextiSelectionForegroundColoriSelectionBackgroundColor。帧之间没有持久缓冲区——着色器是无状态的,因此番茄钟锚定在挂钟时间上。

若你要动手修改,有三点值得注意:

  • Ghostty 的 fragCoord y 轴为自上而下,与其在其他方面遵循的 Shadertoy 约定相反。
  • 要从脚本触发配置重载,请发送 SIGUSR2——但请用 ps 查找 PID,不要用 pgrep/pkill:它们会静默排除自身祖先,而 Ghostty 是任何在其内运行的 shell 的祖先。
  • Claude Code 启动 statusLine/hook 命令时没有控制终端,因此 /dev/tty 在那里会失败——claude-token.py 通过 ps -o ppid=,tty= 遍历其祖先来找到会话的 pty。

License

MIT — 参见 LICENSE

灵感来自 Eric Bruneton 的黑洞着色器(black hole shader)BSD-3-Clause)。本项目未使用该项目中的任何代码——本着色器是独立编写的屏幕空间(screen-space)近似实现,从零开始编写;致谢是针对其思路及其所展示的物理学原理。