Ghostty Devlog 003

Mitchell Hashimoto

Ghostty 开发日志 003

大家好!欢迎阅读 Ghostty 👻 的第三篇正式开发日志!

如果你错过了之前的开发日志,或者想进一步了解 Ghostty 是什么,请查看本网站上的 Ghostty 页面


社区动态:公开 Discord 服务器上线!

我很高兴地宣布,我们现在有了公开的 Discord 服务器!欢迎任何人加入,提问关于 Ghostty 的问题,聊聊终端模拟器、系统编程、GPU 编程等等。我们也在利用这个 Discord 来扩大 beta 计划的规模,所以如果你有兴趣参加 beta 测试,请加入 Discord 服务器并保持一定的活跃度。

接下来,我将于 9 月 5 日在 Zig Showtime 上做一场关于 Ghostty 的演讲。请留意 Zig Showtime 网站、Twitter、Mastodon 等渠道,以获取确切时间和链接的最新消息。演讲内容仍在准备中,但大致主题是对 Ghostty 实现的一次快速巡礼,并重点介绍几个有趣的地方。毕竟这是 Zig Showtime,所以我会更多地聚焦 Ghostty 中与 Zig 相关的部分。


键盘输入处理恨你(#282)1

我只是一个在普通美式标准键盘布局上打字的普通美国用户。在美式标准键盘上,你按 a 就得到 "a",按 '(撇号)就得到 "'"。你按 shift+a 就得到 "A"。简单,太简单了。可你正坐在谎言的王座上。

第一个麻烦的苗头是非标准但以英语为中心的键盘布局,比如 Dvorak 或 Colemak。在使用美式标准物理键盘时,Dvorak 用户按下 s 期望得到 "o",按下 ctrl+i 则期望得到 ctrl+c 的行为。

第二个麻烦的苗头是 Unicode 序列。在 Mac 上、美式标准布局下,alt+a 会产生 å。最新的 beta 用户群中新增了一位挪威用户,他指出这一点尤为重要,因为他偏好美式标准布局,但会使用 alt 组合键来输入挪威语单词。

第三个麻烦的苗头是非标准键盘布局。你知道什么是死键序列(dead key sequence)吗?我不知道(因为我只是一个普通的美式标准布局用户,记得吧)。在美式标准布局下,我们习惯于所有按键都是无状态的。要大写一个字符,你需要同时按下 shift+A。而使用死键序列时,你要先按 shift,松开,再按 a,然后就会看到 A(这并不是真实世界中的死键序列,只是演示死键序列的行为方式)。一个真实的死键序列例子,在美式国际布局上:先按 ',松开,再按 a,你就会得到 á

当一个人在键盘上敲下一个键时,会发生几件事。键盘发送一个物理扫描码(physical scancode)。软件层面的键盘布局处理(即在 QWERTY 键盘上使用 Dvorak 布局)会将该扫描码转换为另一个扫描码(Dvorak 下 "i" 表现得像 "c")。修饰键(ctrl、alt 等)与其他键(比如字母键)的组合可能产生也可能不产生一个或多个字符(例如 macOS 上 alt+p 会产生 "π",但在美式物理键盘的 Dvorak 布局下同样的操作对应的是 alt+r)。

最后,并非所有修饰键都会被消耗掉,这对按键绑定很关键。例如,如果你在美式布局上绑定了 ctrl+=,那么它在挪威语布局上相当于 ctrl+shift+0——你必须知道 shift+0 产生了 =并未消耗 ctrl,因此最终输入是 ctrl+=。但如果用户同时也绑定了 ctrl+shift+0,那该用哪一个呢?🤔

以上解释都非常粗略且缺乏细节。我计划专门写一篇完整的博客文章,来讲述键盘布局、输入处理、终端键盘“协议”以及操作系统级 API 这些克苏鲁式的恐怖存在,所以本篇开发日志中就跳过细节了。敬请期待!

Ghostty 不仅必须处理这些用例(事实证明非美式标准键盘的用户是存在的),而且还必须跨平台做到这一点,因为 Ghostty 已经能在 macOS 和 Linux 上运行,并且还立志支持 Windows。😰 好吧,Ghostty 现在做到了

唯一能展示出来的视觉效果就是死键状态的处理,现在会用最后一个待定的死键来渲染。至于其余部分已经正常工作,你就只能相信我了。

除此之外,Ghostty 现在还支持可选的显式物理键映射(physical key mapping)。如果你将某个按键绑定指定为 ctrl+physical:0(注意 physical: 前缀),那么只有物理位置上的 0 键才会触发该绑定。在某些键盘布局下,这可能使该绑定无法触发。默认情况下,按键绑定基于逻辑(转换后的)键。


Kitty Keyboard Protocol 及其他协议(#292、#295)

如果你已经厌倦了解键盘输入的知识,可以直接跳过这一节。

除了将键盘按键映射为字符输入之外,键盘事件还必须被映射为转义序列(escape sequence),这样在终端中运行的程序才能知道你按下了哪个键。

最初的终端协议只是原样发送由键盘按键产生的字符。你输入 "a",得到 "a";你输入 "A"(shift+A),得到 "A"。这有很多局限性。例如,终端程序无法区分诸如 ctrl-ictrl-shift-i 这样的修饰键组合,因为它们产生的数据完全相同。

为了解决这些缺陷,人们开发了许多额外的键盘协议。xterm 开发了 modified keys 序列,Paul Evans 开发了 fixterms 协议,Kovid Goyal(Kitty 的作者)开发了 Kitty Keyboard Protocol。这些协议的核心就是定义当按键被按下时,终端向正在运行的程序发送什么内容。

这些都不是假设性的问题。tmuxneovim 以及其他主流程序都实现了这些协议,让你能够绑定比原本多出数百个的按键绑定。

Ghostty 现在实现了所有这些协议。

据我所知,Ghostty 是极少数实现了全部这些协议的终端之一。我相信只有 iTerm、Foot,或许还有 WezTerm 同时实现了 xterm modified keys Kitty 协议。我认为实现 Kitty 协议本身的也只有这几款加上 Kitty 自己。大多数终端(Terminal.app、Windows Terminal、Alacritty、Warp)则完全没有实现任何键盘协议。2

下面是一段展示 Kitty keyboard protocol 工作效果的视频:

下面这段视频展示了 xterm modified keys 协议的工作效果:


桌面 Linux 之年(GTK)

最近一批 beta 用户让我们的 Linux 全职用户数从寥寥几人增加到了大约五六人,随之而来的是 Linux 体验方面的许多改进。

首先,GTK 单实例模式(#247)现已启用并获得支持。对于 GTK,Ghostty 现在只运行单个进程。第二次启动 Ghostty 时会在现有进程中打开一个新窗口。这与标准的图形桌面环境配合良好,也让首次启动之后的 Ghostty 启动速度飞快

其次,Ghostty 现在支持主选择剪贴板(primary selection clipboard)(#266)。Linux 桌面拥有多个剪贴板。“primary”(或 “selection”)剪贴板会在文本被选中时自动复制文本,粘贴通常通过鼠标中键完成。这是一个与 ctrl+shift+cctrl+shift+v 所用的剪贴板相互独立的剪贴板,因此你的剪贴板上可以同时存放多项内容。这个功能有时颇具争议,不过别担心,你可以通过 copy-on-select 配置项将其禁用。你可以在下面的视频中看到它的实际效果。注意,我没有按任何键盘快捷键;这里只用到了 selection 剪贴板。

Linux 现已支持自动 shell 集成(#245)。我在开发日志 001 中谈过 shell 集成。此前,Linux 用户必须设置一个环境变量来指向 Ghostty 的 share 目录。现在 Linux 应用会从 ghostty 可执行文件所在位置向上搜索具有正确目录结构的 share 目录。如果你按照标准的 FHS 目录布局(Ghostty 打包所采用的标准布局)安装 Ghostty,那么 Ghostty 会自动找到并配置你的 shell。

还有更多改进:Ghostty 应用图标能正常显示了(#251),GTK 应用可以处理非美式键盘布局了(#271),GTK 应用支持半透明窗口背景了(#264),等等。

我真心希望 Ghostty 能成为一流的基于 GTK 的 Linux 终端。我自己并不全职使用 GTK,所以我非常依赖测试者们提供反馈,来把这份体验打磨到极致。我很高兴地报告,Ghostty 在 GTK 上现在的感觉已经相当不错了!GTK 应用仍然落后于 macOS 版的体验,但我期待着让它达到同等水平!


最近一轮 beta 测试者真是太棒了。我要感谢 @hovsater,他暴露出了所有显而易见的键盘布局和映射问题,并督促我把它们全部修复。@cryptocode 发现了十几个与宽字符、多码点字素及其与选区和软换行交互相关的边界情况 bug。还有许多新的测试者值得感谢,他们为 Linux 方面的改进做出了贡献。谢谢大家!

如果你想持续关注最新动态,可以在 Twitter 或 Mastodon 上关注我(链接见页脚)。本博客也提供 RSS feed

Boo。👻

脚注

  1. 这里致敬的是那篇出色的博客文章 Text Rendering Hates You(《文本渲染恨你》),我强烈推荐阅读。

  2. 我对上述所有终端模拟器的作者都怀有深深的敬意,希望这不会显得像任何攻击。我只是想客观陈述它们各自支持和不支持哪些功能。如果我说错了,请告诉我,我会立即更正。

原文由 Mitchell Hashimoto 发布

本文章由 stealth/ox-alpha 进行翻译