What we need is a terminal emulator, which can:
- accept and parse the mix of data and escape sequences and perform the action accordingly.
- calculate the difference between states of terminal emulator and output mix.
- apply the output mix to replicate the state of terminal emulator.
In real world, the user may choose different terminal emulator. The TERM
environment variable should be propagated from client to the server, just like SSH does. Different TERM
means different terminal capability.
How to adapt to this situation: support different terminal emulator, such as xterm-256color
,
rxvt-256color
, alacritty
, still need a solution.
- First solution: write separate independent parser and actor for different
terminfo
entry. The different implementation share the same terminal emulator data structure. The implementation is chosen byTERM
at runtime. - Second solution: write a base class which can parse and act on a base
terminfo
entry, which could best-256color
. (We don't choosexterm-256color
becausexterm
is too complex) The otherterminfo
entry is implemented by a extend class. The different implementation share the same terminal emulator data structure. The implementation is chosen byTERM
at runtime. - Third solution: a state machine. We choose the third solution because it's the classical solution to our problem.
Both solutions suggest we can support terminfo
entries one by one, step by step. The scope of terminfo
entries can be narrowed down to ncurses-terminfo-base
package (the alpine linux platform).
ide@nvide-ssh:/etc/terminfo $ apk info -L ncurses-terminfo-base
ncurses-terminfo-base-6.3_p20220423-r0 contains:
etc/terminfo/a/alacritty
etc/terminfo/a/ansi
etc/terminfo/d/dumb
etc/terminfo/g/gnome
etc/terminfo/g/gnome-256color
etc/terminfo/k/kitty
etc/terminfo/k/konsole
etc/terminfo/k/konsole-256color
etc/terminfo/k/konsole-linux
etc/terminfo/l/linux
etc/terminfo/p/putty
etc/terminfo/p/putty-256color
etc/terminfo/r/rxvt
etc/terminfo/r/rxvt-256color
etc/terminfo/s/screen
etc/terminfo/s/screen-256color
etc/terminfo/s/st-0.6
etc/terminfo/s/st-0.7
etc/terminfo/s/st-0.8
etc/terminfo/s/st-16color
etc/terminfo/s/st-256color
etc/terminfo/s/st-direct
etc/terminfo/s/sun
etc/terminfo/t/terminator
etc/terminfo/t/terminology
etc/terminfo/t/terminology-0.6.1
etc/terminfo/t/terminology-1.0.0
etc/terminfo/t/terminology-1.8.1
etc/terminfo/t/tmux
etc/terminfo/t/tmux-256color
etc/terminfo/v/vt100
etc/terminfo/v/vt102
etc/terminfo/v/vt200
etc/terminfo/v/vt220
etc/terminfo/v/vt52
etc/terminfo/v/vte
etc/terminfo/v/vte-256color
etc/terminfo/x/xterm
etc/terminfo/x/xterm-256color
etc/terminfo/x/xterm-color
etc/terminfo/x/xterm-xfree86
Although the terminfo
entries number in base package is quit a few, comparing with ncurses-terminfo
package, the base package is just a baby. To narrow down the entries number further, some entries take priority over others, such as xterm-256color
, alacritty
, kitty
, tmux-256color
, putty-256color
.
All the user input is send from real terminal emulator to (Aprish) local client. From pty
master to slave.
On the client side, client read from stand input device, which is a pty
slave.
- The client reads the user input. [user keystroke]
- The client saves the user input in new state. [state saved in
UserStream
] - When it's time to send state to server, [
network.tick()
] - The client calculates the difference between new state and sent states. [
diff_from()
ofUserStream
] - The calculated difference contains
resize
andkeystroke
. - The client sends the difference to server through the network.
On the server side, server receives new state from the network. Then server applies it to server terminal.
- The server receives the difference from client. [
recv()
] - The server use the difference to rebuild the remote state. [
apply_string()
ofUserStream
] - After the remote state is received, [
serve()
] - The server applies the actions to the local terminal emulator. [
serve()
->act()
]- For
keystroke
action, the server applies the input to terminal emulator. - For
resize
action, the server adjusts the size of terminal emulator.
- For
- If there is any response from terminal emulator, the response will be sent back to host application.
On the server side, server receives (terminal) application output from the pty
master, as well as terminal write back.
- When it's time to send state to client, [
network.tick()
] - The server calculates the difference between new state and sent states. [
diff_from()
ofComplete
] - The calculated difference is a mix of escape sequences and data, which contains
echo ack
,resize
,mix
. - The server sends the difference to the client through network.
On the client side, client receives new state from the network. Then the client applies it to local terminal.
- The client receives the difference from server [
recv()
]. - The client applies
resize
,mix
to the local terminal and saveecho ack
. [apply_string()
ofComplete
]
Please note that there are two local termianls. One is used to track received terminal. The other is used to find difference for real terminal emulator, such as kitty
, alacritty
or wezterm
. Here the local terminal changed is the received terminals.
On the client side, the client synchronize the client terminal to real terminal emulator. Here the local terminal is used to find the difference for real terminal.
- When it's time to display the state to real termninal emulator [
output_new_frame()
] - The client fetches the latest received terminal state.
- The client applies the prediction to the new state.
- The client calculates the difference between new state, prediction and local state. [
new_frame()
ofDisplay
]- Here the local state is the last used state.
- The calculated difference is a mix of escape sequences and data.
- The mix is written to the
pty
slave, which will output to the real terminal emulator.
Thus, we have the opportunity to use terminfo to output to the real terminal. Between the client and server, we are free to optimize the state synchronization.
mosh
source code analysis client, server- Unicode 14.0 Character Code Charts
- XTerm Control Sequences
- wezterm Escape Sequences
- Linux man pages
- C++ Reference
- Linux logging guide: Understanding syslog, rsyslog, syslog-ng, journald
- Benchmarking in Golang: Improving function performance
- Golang Field Alignment
- Structure size optimization in Golang (alignment/padding). More effective memory layout (linters)
Need some time to figure out how to support CSI u in aprilsh.