当你在终端上按下一个键时会发生什么？
终端 键盘
![](/data/attachment/album/202207/25/110217dlbzqvm9lltkq244.jpg)


我对<ruby> 终端 <rt>  Terminal </rt></ruby>是怎么回事困惑了很久。


但在上个星期，我使用 [xterm.js](https://xtermjs.org/) 在浏览器中显示了一个交互式终端，我终于想到要问一个相当基本的问题：当你在终端中按下键盘上的一个键（比如 `Delete`，或 `Escape`，或 `a`），发送了哪些字节？


像往常一样，我们将通过做一些实验来回答这个问题，看看会发生什么 : )


### 远程终端是非常古老的技术


首先，我想说的是，用 `xterm.js` 在浏览器中显示一个终端可能看起来像一个新事物，但它真的不是。在 70 年代，计算机很昂贵。因此，一个机构的许多员工会共用一台电脑，每个人都可以有自己的 “终端” 来连接该电脑。


例如，这里有一张 70 年代或 80 年代的 VT100 终端的照片。这看起来像是一台计算机（它有点大！），但它不是 —— 它只是显示实际计算机发送的任何信息。


[![DEC VT100终端](/data/attachment/album/202207/25/110221ize6ezvaehez2za6.jpg)](https://commons.wikimedia.org/wiki/File:DEC_VT100_terminal.jpg "Jason Scott, CC BY 2.0 <https://creativecommons.org/licenses/by/2.0>, via Wikimedia Commons")


当然，在 70 年代，他们并没有使用 Websocket 来做这个，但来回发送的信息的方式和当时差不多。


（照片中的终端是来自西雅图的 <ruby> <a href="https://livingcomputers.org/">  活电脑博物馆 </a> <rt>  Living Computer Museum </rt></ruby>，我曾经去过那里，并在一个非常老的 Unix 系统上用 `ed` 编写了 FizzBuzz，所以我有可能真的用过那台机器或它的一个兄弟姐妹！我真的希望活电脑博物馆能再次开放，能玩到老式电脑是非常酷的。）


### 发送了什么信息？


很明显，如果你想连接到一个远程计算机（用 `ssh` 或使用 `xterm.js` 和 Websocket，或其他任何方式），那么需要在客户端和服务器之间发送一些信息。


具体来说：


**客户端** 需要发送用户输入的键盘信息（如 `ls -l`）。 **服务器** 需要告诉客户端在屏幕上显示什么。


让我们看看一个真正的程序，它在浏览器中运行一个远程终端，看看有哪些信息会被来回发送！


### 我们将使用 goterm 来进行实验


我在 GitHub 上发现了这个叫做 [goterm](https://github.com/freman/goterm) 的小程序，它运行一个 Go 服务器，可以让你在浏览器中使用 `xterm.js` 与终端进行交互。这个程序非常不安全，但它很简单，很适合学习。


我 [复刻了它](https://github.com/jvns/goterm)，使它能与最新的 `xterm.js` 一起工作，因为它最后一次更新是在 6 年前。然后，我添加了一些日志语句，以打印出每次通过 WebSocket 发送/接收的字节数。


让我们来看看在几个不同的终端交互过程中的发送和接收情况吧!


### 示例：ls


首先，让我们运行 `ls`。下面是我在 `xterm.js` 终端上看到的情况：



```
~:/play$ ls
file
~:/play$

```

以下是发送和接收的内容：（在我的代码中，我记录了每次客户端发送的字节：`sent: [bytes]`，每次它从服务器接收的字节：`recv: [bytes]`）



```
sent: "l"
recv: "l"
sent: "s"
recv: "s"
sent: "\r"
recv: "\r\n\x1b[?2004l\r"
recv: "file\r\n"
recv: "\x1b[~:/play$ "

```

我在这个输出中注意到 3 件事：


1. 回显：客户端发送 `l`，然后立即收到一个 `l` 发送回来。我想这里的意思是，客户端真的很笨 —— 它不知道当我输入`l` 时，我想让 `l` 被回显到屏幕上。它必须由服务器进程明确地告诉它来显示它。
2. 换行：当我按下回车键时，它发送了一个 `\r'（回车）符号，而不是`\n'（换行）。
3. 转义序列：`\x1b` 是 ASCII 转义字符，所以 `\x1b[?2004h` 是告诉终端显示什么或其他东西。我想这是一个颜色序列，但我不确定。我们稍后会详细讨论转义序列。


好了，现在我们来做一些稍微复杂的事情。


### 示例：Ctrl+C


接下来，让我们看看当我们用 `Ctrl+C` 中断一个进程时会发生什么。下面是我在终端中看到的情况：



```
~:/play$ cat
^C
~:/play$

```

而这里是客户端发送和接收的内容。



```
sent: "c"
recv: "c"
sent: "a"
recv: "a"
sent: "t"
recv: "t"
sent: "\r"
recv: "\r\n\x1b[?2004l\r"
sent: "\x03"
recv: "^C"
recv: "\r\n"
recv: "\x1b[?2004h"
recv: "~:/play$ "

```

当我按下 `Ctrl+C` 时，客户端发送了 `\x03`。如果我查 ASCII 表，`\x03` 是 “文本结束”，这似乎很合理。我认为这真的很酷，因为我一直对 `Ctrl+C` 的工作原理有点困惑 —— 很高兴知道它只是在发送一个 `\x03` 字符。


我相信当我们按 `Ctrl+C` 时，`cat` 被中断的原因是服务器端的 Linux 内核收到这个 `\x03` 字符，识别出它意味着 “中断”，然后发送一个 `SIGINT` 到拥有伪终端的进程组。所以它是在内核而不是在用户空间处理的。


### 示例：Ctrl+D


让我们试试完全相同的事情，只是用 `Ctrl+D`。下面是我在终端看到的情况：



```
~:/play$ cat
~:/play$

```

而这里是发送和接收的内容：



```
sent: "c"
recv: "c"
sent: "a"
recv: "a"
sent: "t"
recv: "t"
sent: "\r"
recv: "\r\n\x1b[?2004l\r"
sent: "\x04"
recv: "\x1b[?2004h"
recv: "~:/play$ "

```

它与 `Ctrl+C` 非常相似，只是发送 `\x04` 而不是 `\x03`。很好！`\x04` 对应于 ASCII “传输结束”。


### Ctrl + 其它字母呢？


接下来我开始好奇 —— 如果我发送 `Ctrl+e`，会发送什么字节？


事实证明，这只是该字母在字母表中的编号，像这样。


* `Ctrl+a` => 1
* `Ctrl+b` => 2
* `Ctrl+c` => 3
* `Ctrl+d` => 4
* ...
* `Ctrl+z` => 26


另外，`Ctrl+Shift+b` 的作用与 `Ctrl+b` 完全相同（它写的是`0x2`）。


键盘上的其他键呢？下面是它们的映射情况：


* `Tab` -> 0x9（与 `Ctrl+I` 相同，因为 I 是第 9 个字母）
* `Escape` -> `\x1b`
* `Backspace` -> `\x7f`
* `Home` -> `\x1b[H`
* `End` -> `\x1b[F`
* `Print Screen` -> `\x1b\x5b\x31\x3b\x35\x41`
* `Insert` -> `\x1b\x5b\x32\x7e`
* `Delete` -> `\x1b\x5b\x33\x7e`
* 我的 `Meta` 键完全没有作用


那 `Alt` 呢？根据我的实验（和一些搜索），似乎 `Alt` 和 `Escape` 在字面上是一样的，只是按 `Alt` 本身不会向终端发送任何字符，而按 `Escape` 本身会。所以：


* `alt + d` => `\x1bd`（其他每个字母都一样）
* `alt + shift + d` => `\x1bD`（其他每个字母都一样）
* 诸如此类


让我们再看一个例子！


### 示例：nano


下面是我运行文本编辑器 `nano` 时发送和接收的内容：



```
recv: "\r\x1b[~:/play$ "
sent: "n" [[]byte{0x6e}]
recv: "n"
sent: "a" [[]byte{0x61}]
recv: "a"
sent: "n" [[]byte{0x6e}]
recv: "n"
sent: "o" [[]byte{0x6f}]
recv: "o"
sent: "\r" [[]byte{0xd}]
recv: "\r\n\x1b[?2004l\r"
recv: "\x1b[?2004h"
recv: "\x1b[?1049h\x1b[22;0;0t\x1b[1;16r\x1b(B\x1b[m\x1b[4l\x1b[?7h\x1b[39;49m\x1b[?1h\x1b=\x1b[?1h\x1b=\x1b[?25l"
recv: "\x1b[39;49m\x1b(B\x1b[m\x1b[H\x1b[2J"
recv: "\x1b(B\x1b[0;7m  GNU nano 6.2 \x1b[44bNew Buffer \x1b[53b \x1b[1;123H\x1b(B\x1b[m\x1b[14;38H\x1b(B\x1b[0;7m[ Welcome to nano.  For basic help, type Ctrl+G. ]\x1b(B\x1b[m\r\x1b[15d\x1b(B\x1b[0;7m^G\x1b(B\x1b[m Help\x1b[15;16H\x1b(B\x1b[0;7m^O\x1b(B\x1b[m Write Out   \x1b(B\x1b[0;7m^W\x1b(B\x1b[m Where Is    \x1b(B\x1b[0;7m^K\x1b(B\x1b[m Cut\x1b[15;61H"

```

你可以看到一些来自用户界面的文字，如 “GNU nano 6.2”，而这些 `\x1b[27m` 的东西是转义序列。让我们来谈谈转义序列吧！


### ANSI 转义序列


上面这些 `nano` 发给客户端的 `\x1b[` 东西被称为“转义序列”或 “转义代码”。这是因为它们都是以 “转义”字符 `\x1b` 开头。它们可以改变光标的位置，使文本变成粗体或下划线，改变颜色，等等。[维基百科介绍了一些历史](https://en.wikipedia.org/wiki/ANSI_escape_code)，如果你有兴趣的话可以去看看。


举个简单的例子：如果你在终端运行



```
echo -e '\e[0;31mhi\e[0m there'

```

它将打印出 “hi there”，其中 “hi” 是红色的，“there” 是黑色的。[本页](https://misc.flogisoft.com/bash/tip_colors_and_formatting) 有一些关于颜色和格式化的转义代码的例子。


我认为有几个不同的转义代码标准，但我的理解是，人们在 Unix 上使用的最常见的转义代码集来自 VT100（博客文章顶部图片中的那个老终端），在过去的 40 年里没有真正改变。


转义代码是为什么你的终端会被搞乱的原因，如果你 `cat` 一些二进制数据到你的屏幕上 —— 通常你会不小心打印出一堆随机的转义代码，这将搞乱你的终端 —— 如果你 `cat` 足够多的二进制数据到你的终端，那里一定会有一个 `0x1b` 的字节。


### 可以手动输入转义序列吗？


在前面几节中，我们谈到了 `Home` 键是如何映射到 `\x1b[H` 的。这 3 个字节是 `Escape + [ + H`（因为 `Escape` 是`\x1b`）。


如果我在 `xterm.js` 终端手动键入 `Escape` ，然后是 `[`，然后是 `H`，我就会出现在行的开头，与我按下 `Home` 完全一样。


我注意到这在我的电脑上的 Fish shell 中不起作用 —— 如果我键入 `Escape`，然后输入 `[`，它只是打印出 `[`，而不是让我继续转义序列。我问了我的朋友 Jesse，他写过 [一堆 Rust 终端代码](https://github.com/doy/vt100-rust)，Jesse 告诉我，很多程序为转义代码实现了一个 **超时** —— 如果你在某个最小的时间内没有按下另一个键，它就会决定它实际上不再是一个转义代码了。


显然，这在 Fish shell 中可以用 `fish_escape_delay_ms` 来配置，所以我运行了 `set fish_escape_delay_ms 1000`，然后我就能用手输入转义代码了。工作的很好！


### 终端编码有点奇怪


我想在这里暂停一下，我觉得你按下的键被映射到字节的方式是非常奇怪的。比如，如果我们今天从头开始设计按键的编码方式，我们可能不会把它设置成这样：


* `Ctrl + a` 和 `Ctrl + Shift + a` 做的事情完全一样。
* `Alt` 与 `Escape` 是一样的
* 控制序列（如颜色/移动光标）使用与 `Escape` 键相同的字节，因此你需要依靠时间来确定它是一个控制序列还是用户只是想按 `Escape`。


但所有这些都是在 70 年代或 80 年代或什么时候设计的，然后需要永远保持不变，以便向后兼容，所以这就是我们得到的东西 :smiley:


### 改变窗口大小


在终端中，并不是所有你能做的事情都是通过来回发送字节发生的。例如，当终端被调整大小时，我们必须以不同的方式告诉 Linux 窗口大小已经改变。


下面是 [goterm](https://github.com/freman/goterm/blob/a644c10e180ce8af789ea3e4e4892dcf078e97e2/main.go#L110-L115) 中用来做这件事的 Go 代码的样子：



```
syscall.Syscall(
    syscall.SYS_IOCTL,
    tty.Fd(),
    syscall.TIOCSWINSZ,
    uintptr(unsafe.Pointer(&resizeMessage)),
)

```

这是在使用 `ioctl` 系统调用。我对 `ioctl` 的理解是，它是一个系统调用，用于处理其他系统调用没有涉及到的一些随机的东西，通常与 IO 有关，我猜。


`syscall.TIOCSWINSZ` 是一个整数常数，它告诉 `ioctl` 我们希望它在本例中做哪件事（改变终端的窗口大小）。


### 这也是 xterm 的工作方式。


在这篇文章中，我们一直在讨论远程终端，即客户端和服务器在不同的计算机上。但实际上，如果你使用像 xterm 这样的终端模拟器，所有这些工作方式都是完全一样的，只是很难注意到，因为这些字节并不是通过网络连接发送的。


### 文章到此结束啦


关于终端，肯定还有很多东西要了解（我们可以讨论更多关于颜色，或者原始与熟化模式，或者 Unicode 支持，或者 Linux 伪终端界面），但我将在这里停止，因为现在是晚上 10 点，这篇文章有点长，而且我认为我的大脑今天无法处理更多关于终端的新信息。


感谢 [Jesse Luehrs](https://github.com/doy/) 回答了我关于终端的十亿个问题，所有的错误都是我的 :smiley:




---


via: <https://jvns.ca/blog/2022/07/20/pseudoterminals/>


作者：[Julia Evans](https://jvns.ca/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
I’ve been confused about what’s going on with terminals for a long time.

But this past week I was using [xterm.js](https://xtermjs.org/) to display an
interactive terminal in a browser and I finally thought to ask a pretty basic
question: when you press a key on your keyboard in a terminal (like `Delete`

, or `Escape`

, or `a`

), which
bytes get sent?

As usual we’ll answer that question by doing some experiments and seeing what happens :)

### remote terminals are very old technology

First, I want to say that displaying a terminal in the browser with `xterm.js`

might seem like a New Thing, but it’s really not. In the 70s, computers were
expensive. So many employees at an institution would share a single computer,
and each person could have their own “terminal” to that computer.

For example, here’s a photo of a VT100 terminal from the 70s or 80s. This looks like it could be a computer (it’s kind of big!), but it’s not – it just displays whatever information the actual computer sends it.

Of course, in the 70s they didn’t use websockets for this, but the information being sent back and forth is more or less the same as it was then.

(the terminal in that photo is from the [Living Computer Museum](https://livingcomputers.org/) in Seattle which I got to visit once and write FizzBuzz in `ed`

on a very old Unix system, so it’s possible that I’ve actually used that machine or one of its siblings! I really hope the Living Computer Museum opens again, it’s very cool to get to play with old computers.)

### what information gets sent?

It’s obvious that if you want to connect to a remote computer (with `ssh`

or
using `xterm.js`

and a websocket, or anything else), then some information
needs to be sent between the client and the server.

Specifically:

- the
**client**needs to send the keystrokes that the user typed in (like`ls -l`

) - the
**server**needs to tell the client what to display on the screen

Let’s look at a real program that’s running a remote terminal in a browser and see what information gets sent back and forth!

### we’ll use `goterm`

to experiment

I found this tiny program on GitHub called
[goterm](https://github.com/freman/goterm) that runs a Go server that lets you
interact with a terminal in the browser using `xterm.js`

. This program is very insecure but it’s simple and great for learning.

I [forked it](https://github.com/jvns/goterm) to make it work with the latest xterm.js,
since it was last updated 6 years ago. Then I added some logging statements to
print out every time bytes are sent/received over the websocket.

Let’s look at sent and received during a few different terminal interactions!

### example: `ls`


First, let’s run `ls`

. Here’s what I see on the `xterm.js`

terminal:

```
bork@kiwi:/play$ ls
file
bork@kiwi:/play$
```


and here’s what gets sent and received: (in my code, I log `sent: [bytes]`

every time the client sends bytes and `recv: [bytes]`

every time it receives bytes from the server)

```
sent: "l"
recv: "l"
sent: "s"
recv: "s"
sent: "\r"
recv: "\r\n\x1b[?2004l\r"
recv: "file\r\n"
recv: "\x1b[?2004hbork@kiwi:/play$ "
```


I noticed 3 things in this output:

- Echoing: The client sends
`l`

and then immediately receives an`l`

sent back. I guess the idea here is that the client is really dumb – it doesn’t know that when I type an`l`

, I want an`l`

to be echoed back to the screen. It has to be told explicitly by the server process to display it. - The newline: when I press enter, it sends a
`\r`

(carriage return) symbol and not a`\n`

(newline) - Escape sequences:
`\x1b`

is the ASCII escape character, so`\x1b[?2004h`

is telling the terminal to display something or other. I think this is a colour sequence but I’m not sure. We’ll talk a little more about escape sequences later.

Okay, now let’s do something slightly more complicated.

### example: `Ctrl+C`


Next, let’s see what happens when we interrupt a process with `Ctrl+C`

. Here’s what I see in my terminal:

```
bork@kiwi:/play$ cat
^C
bork@kiwi:/play$
```


And here’s what the client sends and receives.

```
sent: "c"
recv: "c"
sent: "a"
recv: "a"
sent: "t"
recv: "t"
sent: "\r"
recv: "\r\n\x1b[?2004l\r"
sent: "\x03"
recv: "^C"
recv: "\r\n"
recv: "\x1b[?2004h"
recv: "bork@kiwi:/play$ "
```


When I press `Ctrl+C`

, the client sends `\x03`

. If I look up an ASCII table,
`\x03`

is “End of Text”, which seems reasonable. I thought this was really cool
because I’ve always been a bit confused about how Ctrl+C works – it’s good to
know that it’s just sending an `\x03`

character.

I believe the reason `cat`

gets interrupted when we press `Ctrl+C`

is that the
Linux kernel on the server side receives this `\x03`

character, recognizes that
it means “interrupt”, and then sends a `SIGINT`

to the process that owns the
pseudoterminal’s process group. So it’s handled in the kernel and not in
userspace.

### example: `Ctrl+D`


Let’s try the exact same thing, except with `Ctrl+D`

. Here’s what I see in my terminal:

```
bork@kiwi:/play$ cat
bork@kiwi:/play$
```


And here’s what gets sent and received:

```
sent: "c"
recv: "c"
sent: "a"
recv: "a"
sent: "t"
recv: "t"
sent: "\r"
recv: "\r\n\x1b[?2004l\r"
sent: "\x04"
recv: "\x1b[?2004h"
recv: "bork@kiwi:/play$ "
```


It’s very similar to `Ctrl+C`

, except that `\x04`

gets sent instead of `\x03`

.
Cool! `\x04`

corresponds to ASCII “End of Transmission”.

### what about Ctrl + another letter?

Next I got curious about – if I send `Ctrl+e`

, what byte gets sent?

It turns out that it’s literally just the number of that letter in the alphabet, like this:

`Ctrl+a`

=> 1`Ctrl+b`

=> 2`Ctrl+c`

=> 3`Ctrl+d`

=> 4- …
`Ctrl+z`

=> 26

Also, `Ctrl+Shift+b`

does the exact same thing as `Ctrl+b`

(it writes `0x2`

).

What about other keys on the keyboard? Here’s what they map to:

- Tab -> 0x9 (same as Ctrl+I, since I is the 9th letter)
- Escape ->
`\x1b`

- Backspace ->
`\x7f`

- Home ->
`\x1b[H`

- End:
`\x1b[F`

- Print Screen:
`\x1b\x5b\x31\x3b\x35\x41`

- Insert:
`\x1b\x5b\x32\x7e`

- Delete ->
`\x1b\x5b\x33\x7e`

- My
`Meta`

key does nothing at all

What about Alt? From my experimenting (and some Googling), it seems like `Alt`

is literally the same as “Escape”, except that pressing `Alt`

by itself doesn’t
send any characters to the terminal and pressing `Escape`

by itself does. So:

- alt + d =>
`\x1bd`

(and the same for every other letter) - alt + shift + d =>
`\x1bD`

(and the same for every other letter) - etcetera

Let’s look at one more example!

### example: `nano`


Here’s what gets sent and received when I run the text editor `nano`

:

```
recv: "\r\x1b[Kbork@kiwi:/play$ "
sent: "n" [[]byte{0x6e}]
recv: "n"
sent: "a" [[]byte{0x61}]
recv: "a"
sent: "n" [[]byte{0x6e}]
recv: "n"
sent: "o" [[]byte{0x6f}]
recv: "o"
sent: "\r" [[]byte{0xd}]
recv: "\r\n\x1b[?2004l\r"
recv: "\x1b[?2004h"
recv: "\x1b[?1049h\x1b[22;0;0t\x1b[1;16r\x1b(B\x1b[m\x1b[4l\x1b[?7h\x1b[39;49m\x1b[?1h\x1b=\x1b[?1h\x1b=\x1b[?25l"
recv: "\x1b[39;49m\x1b(B\x1b[m\x1b[H\x1b[2J"
recv: "\x1b(B\x1b[0;7m GNU nano 6.2 \x1b[44bNew Buffer \x1b[53b \x1b[1;123H\x1b(B\x1b[m\x1b[14;38H\x1b(B\x1b[0;7m[ Welcome to nano. For basic help, type Ctrl+G. ]\x1b(B\x1b[m\r\x1b[15d\x1b(B\x1b[0;7m^G\x1b(B\x1b[m Help\x1b[15;16H\x1b(B\x1b[0;7m^O\x1b(B\x1b[m Write Out \x1b(B\x1b[0;7m^W\x1b(B\x1b[m Where Is \x1b(B\x1b[0;7m^K\x1b(B\x1b[m Cut\x1b[15;61H"
```


You can see some text from the UI in there like “GNU nano 6.2”, and these
`\x1b[27m`

things are escape sequences. Let’s talk about escape sequences a bit!

### ANSI escape sequences

These `\x1b[`

things above that `nano`

is sending the client are called “escape sequences” or “escape codes”.
This is because they all start with `\x1b`

, the “escape” character. . They change the
cursor’s position, make text bold or underlined, change colours, etc. [Wikipedia has some history](https://en.wikipedia.org/wiki/ANSI_escape_code) if you’re interested.

As a simple example: if you run

```
echo -e '\e[0;31mhi\e[0m there'
```


in your terminal, it’ll print out “hi there” where “hi” is in red and “there”
is in black. [This page](https://misc.flogisoft.com/bash/tip_colors_and_formatting) has some nice
examples of escape codes for colors and formatting.

I think there are a few different standards for escape codes, but my understanding is that the most common set of escape codes that people use on Unix come from the VT100 (that old terminal in the picture at the top of the blog post), and hasn’t really changed much in the last 40 years.

Escape codes are why your terminal can get messed up if you `cat`

a bunch of binary to
your screen – usually you’ll end up accidentally printing a bunch of random
escape codes which will mess up your terminal – there’s bound to be a `0x1b`

byte in there somewhere if you `cat`

enough binary to your terminal.

### can you type in escape sequences manually?

A few sections back, we talked about how the `Home`

key maps to `\x1b[H`

. Those 3 bytes are `Escape + [ + H`

(because Escape is
`\x1b`

).

And if I manually type Escape, then [, then H in the
`xterm.js`

terminal, I end up at the beginning of the line, exactly the same as if I’d pressed `Home`

.

I noticed that this didn’t work in `fish`

on my computer though – if I typed
`Escape`

and then `[`

, it just printed out `[`

instead of letting me continue the
escape sequence. I asked my friend Jesse who has written [a bunch of Rust
terminal code](https://github.com/doy/vt100-rust) about this and Jesse told me
that a lot of programs implement a **timeout** for escape codes – if you don’t
press another key after some minimum amount of time, it’ll decide that it’s
actually not an escape code anymore.

Apparently this is configurable in fish with `fish_escape_delay_ms`

, so I ran
`set fish_escape_delay_ms 1000`

and then I was able to type in escape codes by
hand. Cool!

### terminal encoding is kind of weird

I want to pause here for a minute here and say that the way the keys you get pressed get mapped to bytes is pretty weird. Like, if we were designing the way keys are encoded from scratch today, we would probably not set it up so that:

`Ctrl + a`

does the exact same thing as`Ctrl + Shift + a`

`Alt`

is the same as`Escape`

- control sequences (like colours / moving the cursor around) use the same byte
as the
`Escape`

key, so that you need to rely on timing to determine if it was a control sequence of the user just meant to press`Escape`


But all of this was designed in the 70s or 80s or something and then needed to stay the same forever for backwards compatibility, so that’s what we get :)

### changing window size

Not everything you can do in a terminal happens via sending bytes back and forth. For example, when the terminal gets resized, we have to tell Linux that the window size has changed in a different way.

Here’s what the Go code in
[goterm](https://github.com/freman/goterm/blob/a644c10e180ce8af789ea3e4e4892dcf078e97e2/main.go#L110-L115)
to do that looks like:

```
syscall.Syscall(
syscall.SYS_IOCTL,
tty.Fd(),
syscall.TIOCSWINSZ,
uintptr(unsafe.Pointer(&resizeMessage)),
)
```


This is using the `ioctl`

system call. My understanding of `ioctl`

is that it’s
a system call for a bunch of random stuff that isn’t covered by other system
calls, generally related to IO I guess.

`syscall.TIOCSWINSZ`

is an integer constant which which tells `ioctl`

which
particular thing we want it to to in this case (change the window size of a
terminal).

### this is also how xterm works

In this post we’ve been talking about remote terminals, where the client and
the server are on different computers. But actually if you use a terminal
emulator like `xterm`

, all of this works the exact same way, it’s just harder
to notice because the bytes aren’t being sent over a network connection.

### that’s all for now!

There’s definitely a lot more to know about terminals (we could talk more about colours, or raw vs cooked mode, or unicode support, or the Linux pseudoterminal interface) but I’ll stop here because it’s 10pm, this is getting kind of long, and I think my brain cannot handle more new information about terminals today.

Thanks to [Jesse Luehrs](https://github.com/doy/) for answering a billion of my questions about terminals, all the mistakes are mine :)