mosh-server
command parameters.
Usage: mosh-server new [-s] [-v] [-i LOCALADDR] [-p PORT[:PORT2]] [-c COLORS] [-l NAME=VALUE] [-- COMMAND *]
Usage: mosh-server --help
Usage: mosh-server --version
- Run the server
- Serve the client
- How to read from client connection
- Act on terminal
- How to send state to client
Using QUIC for transport? QUIC is too big to understand. I will try the plain UDP first.
int main(int argc, char* argv[]) {
//+--184 lines: folding ----------
try {
return run_server(desired_ip, desired_port, command_path, command_argv, colors, verbose, with_motd);
} catch (const Network::NetworkException& e) {
fprintf(stderr, "Network exception: %s\n", e.what());
return 1;
} catch (const Crypto::CryptoException& e) {
fprintf(stderr, "Crypto exception: %s\n", e.what());
return 1;
}
}
In the main
function: run_server
is the core to start mosh
server.
main()
callsCrypto::disable_dumping_core()
to make sure we don't dump core.main()
parses command parametersmain()
checks port range.main()
prepares shell name and shell command arguments.main()
makes sure UTF8 locale is set.main()
callsrun_server()
with the ip, port and shell path, shell arguments,(etc.) as parameters.
run_server()
gets network idle timeout.run_server()
gets network signaled idle timeout.run_server()
gets initial window size. They will be overwritten by client on first connection.run_server()
callsTerminal::Complete()
to open parser and terminal.run_server()
creates blankNetwork::UserStream
for newtork.run_server()
initializes network, which isServerConnection
.run_server()
sets theverbose
mode vianetwork->set_verbose()
.run_server()
sets theverbose
mode viaSelect::set_verbose()
.run_server()
callsnetwork->port()
to get the port string representation.run_server()
callsnetwork->get_key()
to get the session key string representation.run_server()
prints port and session key to the standard output. The output starts with "MOSH CONNECT ".run_server()
ignores signalSIGHUP
,SIGPIPE
.run_server()
callsfork()
to detach from terminal.- Parent process prints the license information and exits.
- Child process continues.
run_server()
redirectsSTDIN_FILENO
,STDOUT_FILENO
,STDERR_FILENO
to"/dev/null"
for non-verbose mode.run_server()
callsforkpty()
to create a new process operating in a pseudo terminal.
- The child process, which will run a shell process:
- Re-enable signals
SIGHUP
,SIGPIPE
with default value. - Close server-related socket file descriptors, via calling
delete
. - Set terminal UTF8 support.
- Set "TERM" environment variable to be "xterm" or "xterm-256color" based on "-c color" option.
- Set
"NCURSES_NO_UTF8_ACS"
environment variable- to ask ncurses to send UTF-8 instead of ISO 2022 for line-drawing chars.
- Clear
"STY"
environment variable so GNU screen regards us as top level. - Change to the home directory, via calling
chdir_homedir()
: - If
.hushlogin
file don't exist andwith_motd
is true,- print the motd from
"/run/motd.dynamic"
, - or print the motd from
"/var/run/motd.dynamic"
and"/etc/motd"
. - Print warning message if there is unattached mosh session, via calling
warn_unattached()
. - See the following parent process to understand mosh session.
- print the motd from
- Wait for parent to release us, via calling
fgets()
forstdin
. - Enable core dump, via calling
Crypto::reenable_dumping_core()
. - Execute the shell command with arguments, via calling
execvp()
. - If error happens during
execvp()
, Terminate the child process, via callingexit()
.
- Re-enable signals
- The parent process, which will run the mosh server process:
- Add utmp record via calling
utempter_add_record()
,- with
master
as pty master parameter,"mosh [%ld]"
as host name parameter. - as login service update utmp record is required.
- with
- Serve the client, via calling
serve()
.- with
master
,terminal
,network
as parameters.
- with
- Delete utmp record via calling
utempter_remove_record()
,- with
master
as pty master parameter. - as login service update utmp record is required.
- with
- Close the master pseudo-terminal.
- Close server-related socket file descriptors, via calling
delete
. - Print exiting message.
- Add utmp record via calling
ServerConnection
akaNetwork::Transport<Terminal::Complete, Network::UserStream>
.Network::Transport
is constructed with:- the
terminal
which is type ofTerminal::Complete
, - the
blank
which is type ofUserStream
, ip
,port
as parameters.
- the
Network::Transport
has aConnection
, which represents the underlying, encrypted network connection.Network::Transport
has aTransportSender<Terminal::Complete>
, which represents the sender.Network::Transport
has alist<TimestampedState<Network::UserStream>>
, which represents receiver.Network::Transport
callsconnection(desired_ip, desired_port)
to initialize the connection.Network::Transport
callssender(connection, initial_state)
to initialize sender.- In the constructor of
Network::Transport
,received_states
is a list type ofTimestampedState<Network::UserStream>
.received_states
is initialized with theblank
as parameter.received_states
adds theblank
to its list.
Network::Transport()
setreceiver_quench_timer
to zero.Network::Transport()
setlast_receiver_state
to beterminal
.Network::Transport()
createsfragments
, which is type ofFragmentAssembly
.
connection(desired_ip, desired_port)
is called to create the connection with server.connection()
is the constructor ofNetwork::Connection
connection()
initializes a empty deque ofSocket
:socks
.
connection()
initializeshas_remote_addr
to true.connection()
initializes thekey
, which is type ofBase64Key
Base64Key
reads 16 bytes from/dev/urandom
as thekey
.
connection()
initializessession
withkey
as parameter,session
object is used to encrypt/decrypt message.
connection()
callssetup()
to set thelast_port_choice
to current time.connection()
callsparse_portrange()
to parse port range fromdesired_port
parameter.connection()
callstry_bind()
to bind the port to network interface.- If
desired_ip
is given, usedesired_ip
as parameter to calltry_bind()
. try_bind()
is called with port range parameters.
- If
connection()
returns iftry_bind()
returns true.
sender(connection, initial_state)
is called to initialize the sender.sender()
is the constructor ofTransportSender<Terminal::Complete>
.sender()
initializesconnection
pointer with theconnection
as parameter.sender()
initializescurrent_state
with theinitial_state
as parameter.sender()
initializessent_states
list with theinitial_state
as the first state.
try_bind()
initializes aAddrInfo
object withdesired_ip
as parameter.AddrInfo
callsgetaddrinfo()
to get theaddrinfo
object.
try_bind()
creates aSocket
and pushes it intosocks
deque.Socket
usessetsockopt()
to set socket options:IP_MTU_DISCOVER
,IP_TOS
,IP_RECVTOS
.
try_bind()
searches the port range, callsbind()
bind the server socket to that port.- If
bind()
returns successfully.try_bind()
callsset_MTU()
to set the MTU and return true. - If
bind()
fails,try_bind()
throw exceptions and return false.
- If
network->port()
callsconnection.port()
to get the port.connection.port()
callsgetsockname()
to get thesockaddr
of server socket.connection.port()
callsgetnameinfo()
to get the port string representation.
network->get_key()
callsconnection.get_key()
to get the session key.connection.get_key()
callskey.printable_key()
instead.key.printable_key()
akaBase64Key::printable_key()
.key.printable_key()
callsbase64_encode()
to show thekey
base64 representation.
- Get the
termios
struct forSTDIN_FILENO
, via callingtcgetattr()
. - Set
IUTF8
flag fortermios
. - Set the
termios
struct forSTDIN_FILENO
, via callingtcsetattr()
.
- Call
getenv()
orgetpwuid(getuid())
to get thehome
path. - Call
chdir()
to change to thehome
path. - Call
setenv()
to set the"PWD"
environment variable.
warn_unattached()
callsgetpwuid(getuid())
to get the current user.warn_unattached()
checks the records inutmp
file- If the
ut_user
field is the same user as the current user, via callinggetpwuid(getuid()
, - If the
ut_type
field isUSER_PROCESS
, - If the
ut_host
field does look like"mosh [%ld]"
, where%ld
is the process ID, - If the
ut_host
field isn't equal toignore_entry
, which is the mosh session, - If pseudo-terminal device identified by the
ut_line
field exist, - Pushes the
ut_host
intounattached_mosh_servers
vector.
- If the
warn_unattached()
returns ifunattached_mosh_servers
vector is empty.warn_unattached()
prints warning message toSTDOUT
, if there exists unattached sessions.
serve()
initializes the singletonSelect
object:sel
.serve()
registers signal handler insel
, forSIGTERM
,SIGINT
,SIGUSR1
.serve()
gets the latest remote state number, via callingnetwork.get_remote_state_num()
.serve()
setschild_released
to false.serve()
setsconnected_utmp
to false, initializessaved_addr
andsaved_addr_len
to zero and false.
In the main loop(while loop), It performs the following steps:
- Calculate
timeout
based onnetwork.wait_time()
andterminal.wait_time()
. - Clear file descriptor, via calling
sel.clear_fds()
. - Get network socket, via calling
network->fds()
,fd_list.back()
. - Add network socket to
Select
object. - Add pty master to
Select
object. - Wait for socket input, signal, pty master input via calling
sel.select()
, with thetimeout
as parameter. - Upon receive signals, the corresponding item in
Select.got_signal
array is set. - Upon network sockets is ready to read, read it with
network.recv()
.- After
network.recv()
, the remote state is saved inreceived_states
, - and the opposite direction
ack_num
is saved. - if remote state number is not equal to
last_remote_num
, prepare input for terminal.
- After
- Upon pty master input is ready to read, read it via calling
read()
system call.- If it does read some input, call string version of
terminal.act()
to process the input data. - append the return value of
terminal.act()
toterminal_to_host
. - set current state via calling
network.set_current_state()
with theterminal
as parameter.
- If it does read some input, call string version of
- Write user input and terminal write back to the host via calling
swrite()
.- All data collected in
terminal_to_host
is write to the pty master. - Why terminal write back? See Terminal write back
- All data collected in
- If server doesn't receive client data over
network_timeout_ms
, setidle_shutdown
true. - Upon receive
SIGUSR1
signal, checknetwork_signaled_timeout_ms
to decide how to setidle_shutdown
.SIGUSR1
signal is used to kill the mosh-server gracefully.
- If
idle_shutdown
is true or receive any signal, prepare to shutdown. - Quit if our shutdown has been acknowledged.
- Quit after shutdown acknowledgement timeout.
- Quit if we received and acknowledged a shutdown request.
- Update utmp if has been more than 30 seconds since heard from client.
- Check the echo condition and decide whether to echo ack via calling
terminal.set_echo_ack()
.terminal.set_echo_ack()
akaComplete::set_echo_ack()
terminal.set_echo_ack()
checksinput_history
time againstnow
andECHO_TIMEOUT
.terminal.set_echo_ack()
updatesinput_history
andecho_ack
.- If
terminal.set_echo_ack()
returns true, - set current state via calling
network.set_current_state()
with theterminal
as parameter.
- Quit if there is no connection within 60 seconds.
- Perform
network->tick()
to synchronizes the data to the client.
- Update
last_remote_num
with the latest one. - Initialize a empty
UserStream
object:us
. - Find the difference between new state and
last_receiver_state
, via callingnetwork.get_remote_diff()
. - Initialize the
UserStream
from the above difference string via callingapply_string()
. - Iterate through the above
us
,- get the
action
object of typeParser::Action
via callingus.get_action()
. UserEvent
object containsParser::UserByte
object orParser::Resize
object.Parser::UserByte
andParser::Resize
are sub-class ofParser::Action
.- For
Resize
action:- skip the consecutive Resize action,
- convert the action into
Parser::Resize
, - get the window size for
STDIN_FILENO
, viaioctl()
andTIOCGWINSZ
flag, - set the window size for
STDIN_FILENO
, viaioctl()
andTIOCSWINSZ
flag.
- For other action:
- call
Action
version ofterminal.act()
to get the transcript character. - append the transcript character to
terminal_to_host
.
- call
- get the
- If
us
is not empty,- register input frame number for future echo ack via calling
terminal.register_input_frame()
.
- register input frame number for future echo ack via calling
- Set the current state via calling
network.set_current_state()
, ifnetwork
is not shutdown. - If
connected_utmp
is false andsaved_addr_len
is different fromnetwork.get_remote_addr_len()
,- delete
utmp
record via callingutempter_remove_record()
, with pty master as parameter. - store the value from
network.get_remote_addr()
intosaved_addr_len
, - store the value from
network.get_remote_addr_len()
intosaved_addr_len
, - get the
host
name via callinggetnameinfo()
, - add
utmp
record via callingutempter_add_record
, with pty master andhost
as parameters. - set
connected_utmp
to true.
- delete
- If
child_released
is false, release the child process via writing\n
to pty master,- upon receive the empty line input, the child process will start the shell.
- set
child_released
to true.
get_remote_diff()
akaTransport<MyState, RemoteState>::get_remote_diff()
.- Here
RemoteState
isUserStream
. get_remote_diff()
callsdiff_from()
to calculate diff.diff_from()
to compare the newestreceived_states
withlast_receiver_state
.diff_from()
returns the difference string representation of theClientBuffers::UserMessage
object.
- Next
get_remote_diff()
rationalizesreceived_states
.get_remote_diff()
sets theoldest_receiver_state
with the value of the oldestreceived_states
.get_remote_diff()
iterates through thereceived_states
list in reverse order (newest to oldest).oldest_receiver_state
is the target to be evluated for each iteration.- For each iterating state, calls
UserStream::subtract()
to subtract sharedUserEvent
. get_remote_diff()
stores the newestreceived_states
inlast_receiver_state
.
get_remote_diff()
returns the difference string representation ofClientBuffers::UserMessage
.
For UserStream
:
apply_string()
akaUserStream::apply_string()
.apply_string()
creates aClientBuffers::UserMessage
object.apply_string()
parses string representation ofClientBuffers::UserMessage
.apply_string()
iterates throughClientBuffers::UserMessage
object.- For each iteration,
apply_string()
builds and pushesUserEvent
intoUserStream.actions
.apply_string()
extractsUserByte
orResize
fromClientBuffers::UserMessage
.apply_string()
wrapsUserByte
orResize
inUserEvent
.
- That means
apply_string()
initializesUserStream
withClientBuffers::UserMessage
.
For Complete
:
apply_string()
akaComplete::apply_string
.apply_string()
creates aHostBuffers::HostMessage
object.apply_string()
parses string representation ofHostBuffers::HostMessage
.apply_string()
iterates throughHostBuffers::HostMessage
object.- For each iteration,
apply_string()
checks the instruction extension:- If the extension is
HostBytes
,apply_string()
calls string version ofact()
to change the terminal. - if the extension is
ResizeMessage
,apply_string()
callsAction
version ofact()
to change the terminal. - if the extension is
EchoAck
,apply_string()
extractsecho_ack_num
and set it inecho_ack
field.
- If the extension is
terminal.act()
akaComplete::act()
.terminal.act()
has aAction
as parameter.terminal.act()
apply action to terminal via callingact->act_on_terminal()
with theterminal
as parameter.- For
UserByte
action,UserByte::act_on_terminal
will store the transcript character todispatch.terminal_to_host
. - For
Resize
action,Resize::act_on_terminal
will change the terminal frame buffer size.
- For
terminal.act()
returnsterminal.read_octets_to_host()
.terminal.read_octets_to_host()
akaEmulator::read_octets_to_host()
.terminal.read_octets_to_host()
readsdispatch.terminal_to_host
toret
.terminal.read_octets_to_host()
clearsdispatch.terminal_to_host
.terminal.read_octets_to_host()
returnsret
.
- The above implementation means
terminal.act()
return the transcript character to caller.
act_on_terminal()
has parameterTerminal::Emulator* emu
.act_on_terminal()
callsemu->user.input()
to convert user's cursor control sequence to ANSI cursor control sequence.emu->user.input()
hasUserByte
parameter andapplication_mode_cursor_keys
parameter.emu->user.input()
checks theUserByte
parameter.- For
Ground
character,emu->user.input()
returns the raw character string. - For "0x1b" character,
emu->user.input()
sets state toESC
and returns raw character string. - If state is
ESC
and character isO
,emu->user.input()
sets state toSS3
and return empty string.
- If state is
ESC
and character isn'tO
,emu->user.input()
sets state toGround
and return raw character string.
- If state is
SS3
and character isn'tA-D
andapplication_mode_cursor_keys
is false,emu->user.input()
sets state toGround
and returnESC [ [A-D]
string.
- If state is
SS3
and character isn'tA-D
andapplication_mode_cursor_keys
is true,emu->user.input()
sets state toGround
and returnESC O [A-D]
string.
- See PC-Style Function Keys for application cursor mode.
act_on_terminal()
appends the above string to terminaldispatch.terminal_to_host
.- via calling
emu->dispatch.terminal_to_host.append()
to
- via calling
act_on_terminal()
returns void.
act_on_terminal()
has parameterTerminal::Emulator* emu
.act_on_terminal()
callsemu->resize()
to adjust terminal frame buffer size.emu->resize()
akaEmulator::resize()
.emu->resize()
hass_width
ands_height
parameter.emu->resize()
callsfb.resize()
to finish the job.fb.resize()
akaFramebuffer::resize()
.fb.resize()
hass_width
ands_height
as parameters.fb.resize()
adjustFramebuffer.ds
size according to the width and height parameters.fb.resize()
adjustFramebuffer.row
size according to the width and height parameters.- The above implementation means the
Framebuffer.ds
andFramebuffer.row
is changed according to the parameters.
act_on_terminal()
returns void.
terminal.act(string)
akaComplete::act(string)
.terminal.act(string)
has a string as parameter.- Iterate the string parameter,
- For each character, call
parser.input()
to parse octet into up to three actions.- Iterate the
actions
, for each action, - apply action to terminal via calling
act->act_on_terminal()
with theterminal
as parameter.
- Iterate the
- Clear the
actions
via callingactions.clear()
. - Return
terminal.read_octets_to_host()
.read_octets_to_host()
akaEmulator::read_octets_to_host
.read_octets_to_host()
stores the value ofdispatch.terminal_to_host
.read_octets_to_host()
clears the value ofdispatch.terminal_to_host
.read_octets_to_host()
returns the stored value ofdispatch.terminal_to_host
.
There are a lot of Action
. Their act->act_on_terminal()
is different form each other. The following is some examples.
- UserByte::act_on_terminal
- Resize::act_on_terminal
- Print::act_on_terminal
- CSI_Dispatch::act_on_terminal
- The first
parser.input()
is actuallyParser::UTF8Parser::input()
. - If ASCII code of character is less than "0x7f" and
buf_len
is 0,- call the second
parser.input()
with theactions
as parameter. actions
akaComplete.actions
, a vector of typeParser::Action
- return early.
- call the second
- Assign the
c
tobuf[buf_len++]
. - Parse the
buf
fields ofParser::UTF8Parser
in a loop.- According to Unicode 6.0, section 3.9 Best Practices for using U+FFFD.
- Convert multi-byte sequence to wide character via calling
mbrtowc()
. - Call the second
parser.input()
for the wide character with theactions
as parameter. - Continue the loop until all byte is parsed.
- The second
parser.input()
is actuallyParser::Parser::input()
. parser.input()
callsstate->input()
to parse the wide character toTransition
.parser.input()
callsappend_or_delete()
iftx.next_state
is not NULL.append_or_delete()
decides whether to push theAction
:state->exit()
intoactions
.parser.input()
callsappend_or_delete()
to push theAction
:tx.action
intoactions
.parser.input()
clearstx.action
.parser.input()
callsappend_or_delete()
iftx.next_state
is not NULL.append_or_delete()
decides whether to push theAction
:tx.next_state->enter()
intoactions
.parser.input()
updatesstate
withtx.next_state
.
state->input()
parses the character intoTransition
via callinganywhere_rule()
.anywhere_rule()
createsTransition
based on character coding rule.
- If the created
Transition.next_state
is not empty,state->input()
assigns value tochar_present
andch
fields ofTransition.action
.- returns early with created
Transition
.
state->input()
parses the character intoTransition
via callingthis->input_state_rule()
.this->input_state_rule()
parses high Unicode code-points.- The behaviour of
this->input_state_rule()
depends on the implementation ofState
sub-class. - The default
State
isPaser:Ground
. Ground::input_state_rule()
parses character according toC0_prime
rule andGLGR
rule.C0_prime
rule returns aTransition
whoseaction
field isParser::Execute
.GLGR
rule returns aTransition
whoseaction
field isParser::Print
.Ground::input_state_rule()
returns the secondTransition
whoseaction
field isParser::Ignore
.
state->input()
assigns value tochar_present
andch
fields of the secondTransition.action
.- Return with the second created
Transition
.
- Action = Execute; State = Ground - "0x18, 0x1A, 0x80…8F, 0x91…97, 0x99, 0x9A"
- Action = Ignore; State = Ground - "0x9C"
- Action = Ignore; State = Escape - "0x1B"
- Action = Ignore; State = SOS_PM_APC_String - "0x98, 0x9E, 0x9F"
- Action = Ignore; State = DCS_Entry - "0x90"
- Action = Ignore; State = OSC_String - "0x9D"
- Action = Ignore; State = CSI_Entry - "0x9B"
- See List of Unicode characters
- See ANSI escape code in wikipedia
- See C0 and C1 control codes
- Action = Ignore; State = Execute — "0x19, 0x00…17, 0x1C…1F"
- See XTerm Control Sequences
- Action = Ignore; State = Print — "0x20…0x7F, 0xA0…FF",
- See XTerm Control Sequences
Upon server network socket is ready to read, connection->recv()
starts to read it.
Connection::recv_one()
reads UDP datagram from server socket.Connection::recv_one()
decrypts the UDP datagram intoCrypto::Message
.Crypto::Message
is a utility class for crypto.Crypto::Message
contains the following fields:Nonce
: containsdirection
andseq
fields inNetwork::Packet
.text
: containstimestamp
,timestamp_reply
andpayload
fields inNetwork::Packet
.
Connection::recv_one()
transformsCrypto::Message
intoNetwork::Packet
.Network::Packet
belongs to in datagram layter.Network::Packet
contains the following fields:seq
,timestamp
,timestamp_reply
,payload
,direction
.
Connection::recv_one()
returns string representation ofNetwork::Packet
paylod field.Connection::recv()
returns string representation ofNetwork::Packet
payload field.Fragment()
transformNetwork::Packet
intoNetwork::Fragment
Network::Fragment
contains the following fields:id
,fragment_num
,final
,contents
.
fragments.get_assembly()
akaFragmentAssembly::get_assembly()
.fragments.get_assembly()
concatenates thecontents
field of eachNetwork::Fragment
into one string.fragments.get_assembly()
decompress the string and transforms it intoTransportBuffers.Instruction
.TransportBuffers.Instruction
is the "state" in transport layter.TransportBuffers.Instruction
contains the following fields:old_num
,new_num
,ack_num
,throwaway_num
,diff
.
connection->recv()
creates an emptyTimestampedState<Network::UserStream>
.Network::UserStream
is wrapped inTimestampedState<Network::UserStream>
.TimestampedState<Network::UserStream>
contains the following fields:timestamp
,num
,state
.
network.get_remote_diff()
compares the newestNetwork::UserStream
and existingNetwork::UserStream
.network.get_remote_diff()
returns the difference string representation ofClientBuffers::UserMessage
.UserStream.apply_string()
initializesUserStream
withClientBuffers::UserMessage
.Network::UserStream
contains a deque of typeNetwork::UserEvent
.Network::UserEvent
contains the following fields:type
,userbyte
,resize
.
connection->recv()
storesTimestampedState<Network::UserStream>
inreceived_states
.
The act_on_terminal()
method for all kinds of Action
is different. To understand the design, first you need to understand how to register function. Then we will give you several solid examples to see the effect of act_on_terminal()
.
- There is a singleton
DispatchRegistry
, which is a global dispatch registry. DispatchRegistry
contains three map, which is type ofmap<std::string, Function>
.DispatchRegistry
hasescape
map,CSI
map,control
map.get_global_dispatch_registry()
akaTerminal::get_global_dispatch_registry
.get_global_dispatch_registry()
returns theDispatchRegistry
singleton object.- The constructor of
Function
class insert a pair ofstring,Function
into the specified map type. - There are a lot of
Function
instances registered inDispatchRegistry
.
See the following example to get a solid understanding. The last parameter of Function
constructor is a function. It has a Framebuffer
parameter and a Dispatcher
parameter and returns void.
static Function func_CSI_EL(CSI, "K", CSI_EL);
static Function func_CSI_ED(CSI, "J", CSI_ED);
static Function func_CSI_cursormove_A(CSI, "A", CSI_cursormove);
static Function func_CSI_cursormove_B(CSI, "B", CSI_cursormove);
static Function func_CSI_cursormove_C(CSI, "C", CSI_cursormove);
static Function func_CSI_cursormove_D(CSI, "D", CSI_cursormove);
static Function func_CSI_DSR(CSI, "n", CSI_DSR);
…
static Function func_Ctrl_BEL(CONTROL, "\x07", Ctrl_BEL);
static Function func_Ctrl_LF(CONTROL, "\x0a", Ctrl_LF);
…
static Function func_Esc_DECSC(ESCAPE, "7", Esc_DECSC);
static Function func_Esc_DECRC(ESCAPE, "8", Esc_DECRC);
…
act_on_terminal()
callsemu->print()
to do the job, with current action as parameter.emu->print()
akaEmulator::print()
.- Check for printing ISO 8859-1 first, to get the character width.
- Get the cursor position cell from frame buffer:
this_cell
. - Check the character width:
- In case of normal character or wide character:
- If it's wrap mode or should wrap,
- set the cursor row wrap;
- move to column 0 in
fb.ds
; - scroll the row by one;
this_cell
is NULL.
- If wrap mode is true, character width is two, cursor column reaches the screen width.
- reset the
this_cell
; - set the cursor row wrap;
- move to column 0 in
fb.ds
; - scroll the row by one;
this_cell
is NULL.
- reset the
- If it's insert mode in
fb.ds
,- insert cell according to the character width;
this_cell
is NULL.
- Get the cursor position cell from frame buffer:
this_cell
. Ifthis_cell
is NULL. - Reset the
this_cell
; - Append the character;
- Set cell wide;
- Apply rendition to cell.
- If it's wide character, erase overlapped cell.
- Move cursor to next position in
fb.ds
.
- If it's wrap mode or should wrap,
- In case of combining character:
- Get the combining cell from frame buffer:
combining_cell
. - If cell starts with combining character,
- set cell fall back;
- move cursor to next position in
fb.ds
.
- If cell is not full (32 is the limit by mosh),
- append the character to
combining_cell
.
- append the character to
- Get the combining cell from frame buffer:
- For other case, just ignore it.
act_on_terminal()
callsemu->CSI_dispatch()
to do the job, with current action as parameter.emu->CSI_dispatch()
akaEmulator::CSI_dispatch()
.CSI_dispatch()
callsdispatch.dispatch()
to do the job, withCSI
, action andFramebuffer
as parameter.dispatch.dispatch()
akaDispatcher::dispatch()
.dispatch()
duplicates the action and saves it inDispatcher.dispatch_chars
via callingcollect()
.dispatch()
find theCSI
map in singletonDispatchRegistry
object.dispatch()
createskey
with theact->ch
.dispatch()
looks up thekey
inCSI
map.- If
dispatch()
finds theFunction
with the specifiedkey
,- it sets
fb->ds.next_print_will_wrap
according to the value ofFunction.clears_wrap_state
field. - it call the
Function
with frame buffer andDispatcher
object as parameters.
- it sets
- If
dispatch()
does not find theFunction
inCSI
map.- it sets
fb->ds.next_print_will_wrap
false.
- it sets
If the escape sequence is "ESC [ D", func_CSI_cursormove_D
will be found, CSI_cursormove()
will be called.
CSI_cursormove()
callsdispatch->getparam()
to get thenum
, default value is 1.CSI_cursormove()
callsdispatch->get_dispatch_chars()
to get the command.CSI_cursormove()
callsfb->ds.move_col()
to move the cursor.
If the escape sequence is "ESC [ 5 n", func_CSI_DSR
will be found. CSI_DSR()
will be called.
CSI_DSR()
callsdispatch->getparam()
to get the first parsed parameter: which is int 5 in this case.CSI_DSR()
prepares the responding escape sequence according to the parameter 5.- The responding sequence is "ESC [ 0 n".
- The responding sequence is append to
dispatch->terminal_to_host
. - For the request: "ESC [ 6 n", the response is "ESC [ r ; c R", Report Cursor Position (CPR).
The terminal emulator works like a real terminal emulator. It responds the escape sequence with proper action.
The following Function
write escape sequence response to the dispatch->terminal_to_host
.
/* device attributes */
static Function func_CSI_DA(CSI, "c", CSI_DA);
/* secondary device attributes */
static Function func_CSI_SDA(CSI, ">c", CSI_SDA);
/* device status report -- e.g., cursor position (used by resize) */
static Function func_CSI_DSR(CSI, "n", CSI_DSR);
Please remember: UserByte.act_on_terminal()
also write to dispatch->terminal_to_host
.
void UserByte::act_on_terminal(Terminal::Emulator* emu) const {
emu->dispatch.terminal_to_host.append(emu->user.input(this, emu->fb.ds.application_mode_cursor_keys));
}
Upon pty master is ready to read, serve()
starts to read it.
serve()
reads bytes from pty master.serve()
callsterminal.act()
to change the terminal emulator.terminal.act()
callsparser.input()
to parse each byte intoAction
and saves it inactions
.terminal.act()
iterates through theactions
vector, callingact->act_on_terminal()
for each action.- After
act->act_on_terminal()
, terminal emulator changed according to the pty master input.
serve()
callsnetwork.set_current_state()
to set theterminal
ascurrent_state
.
serve()
callsnetwork.tick()
to send current state to client.tick()
callscurrent_state.diff_from()
to calculate the states difference.Complete::diff_from()
calculates difference using existing and current terminal frame buffer.Complete::diff_from()
buildsHostBuffers::HostMessage
HostBuffers::HostMessage
congtainsEchoAck
,ResizeMessage
,HostBytes
.Display::new_frame()
calculatesHostBytes
by considering the following scenario:- bell ring, icon/window title, reverse vido, window size, cursor visibility, scroll Display,
- cursor location, renditions, bracketed paste mode, mouse reporting mode, mouse focus mode,
- mouse encoding mode
Display::new_frame()
generates escape sequences to get the string representation of frame buffer.
send_in_fragments()
transformsHostBuffers::HostMessage
intoTransportBuffers::Instruction
.- For the rest of sending process, see here.