by John Cowan and Harold Ancell
This SRFI is currently in withdrawn status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-205@nospamsrfi.schemers.org
. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
editor's summary of reasons for withdrawal: It has been 604 days since the first draft, and Harold agreed that it was unlikely to be finished in the next several months.
This SRFI describes procedures for command-line and terminal interface programs to safely change and reset terminal modes, for example from cooked to raw and back, and for serial-line device manipulation for interfacing with embedded hardware and the like.
It is intended to provide all the termios structure functionality a modern Scheme programmer might desire by supplying a stty procedure, and simple abstractions on top of it.
It also provides a set of miscellaneous POSIX terminal procedures that don't belong anywhere else.
Are there any modern needs for exposed stty(1)
functionality besides serial-line manipulation for communication with
other systems? (This SRFI does not propose to support or hinder
retrocomputing with real serial-line terminals.)
Exactly which stty(1)
arguments are needed for modern
serial-line use, such as with embedded computing?
POSIX provides a complete set of routines for manipulating terminal devices — putting them in "raw" mode, changing and querying their special characters, modifying their I/O speeds, and so forth. Now that terminal emulators have almost completely displaced terminals, very little of this is useful except for directly controlling serial-line hardware, which is still widely used for embedded programming and applications.
This SRFI provides safe high-level wrappers for raw, no echo, etc.
terminal modes for use by command-line programs and textual user
interfaces (TUIs), and a stty(1)
style procedure to set a
terminal's characteristics such as its baud rate.
These two use cases are bundled together in one SRFI because
the complicated low-level infrastructure for implementing either is
roughly the same, except for different sets of constants, which are
presumably cheap to implement. If you first implement
stty
, the special terminal mode procedures can be
simply and elegantly built on top of it with a few lines of code.
Various procedures or their equivalents in SRFI 170: POSIX API, from which it was forked off of, are needed or may be useful in concert with it. It is based on the 2018 edition of POSIX IEEE Std 1003.1-2017™.
Throughout this specification, it is an error if port is not open on a terminal.
The procedures of this SRFI raise an exception when an underlying system call fails in the style of SRFI 170. It provides three procedures which will typically be shims over whatever the implementation uses to report such errors:
(posix-error? obj)
→ boolean
This procedure returns
#t
ifobj
is a condition object that describes a POSIX error, and#f
otherwise.
(posix-error-name posix-error)
→ symbol
This procedure returns a symbol that is the name associated with the value of
errno
when the POSIX function reported an error. This can be used to provide programmatic recovery when a POSIX function can return more than one value oferrno
.Because the
errno
codes are not standardized across different POSIX systems, but the associated names (bound by a#define
in the file/usr/include/errno.h
) are the same for the most part, this function returns the name rather than the code.For example,
ENOENT
(a reference was made to a file or a directory that does not exist) almost always corresponds to anerrno
value of 2. But althoughETIMEDOUT
(meaning that a TCP connection has been unresponsive for too long) is standardized by POSIX, it has aerrno
value of 110 on Linux, 60 on FreeBSD, and 116 on Cygwin.
(posix-error-message posix-error)
→ string
This procedure returns a string that is an error message reflecting the value of
errno
when the POSIX function reported an error. This string may be, or may include, the output of thestrerror()
function, and is useful for reporting the cause of the error to the user. It may or may not be localized.
This SRFI also recommends (but does not require) that the following additional information be retrievable by other means:
errno
(an exact integer)errno
The following procedures conform to an implementation model
that represents how they may be implemented.
A state object is a hidden object that represents
the complete state of a terminal line
as retrieved by tcgetattr()
and set by tcsetattr()
.
An implementation of this SRFI maintains a stack of state objects;
a new element can be pushed, the top element can be popped, and the bottom
element can be retrieved without popping it.
The stack is initialized with a single state object representing
the state of the terminal when the program starts.
In practice this may be done on the first call to any of this
section's with-*
or without-*
procedures.
The procedures use dynamic-wind
when executing
their proc argument.
In the before-thunk the current terminal state is fetched
from a specified place and is pushed on the stack. Then specified flags
are turned on or off, and the terminal is set according to the resulting state.
In the after-thunk a terminal state is popped from the stack
and the terminal is set accordingly.
If proc
's dynamic extent is escaped,
the after-thunk is executed, and if control returns to the
dynamic extent, the before-thunk is executed.
The general paradigm for using with-raw-mode
and with-rare-mode
is to set up your application, then
run it in the proc
provided to them, a procedure
that takes the same port arguments in the same order as the
containing with-
or without-
procedure.
Inside proc
, with-cooked-mode
can be used for a temporary escape, for instance to a shell.
The without-echo
procedure is an exception in that it's
generally used to enter a password or passphrase. The procedures
return the values that proc
returns.
(with-raw-mode port proc min time)
→ [values] (procedure) POSIX tcgetattr(), tcsetattr()
The terminal is set to raw mode during the dynamic execution of
proc
and then is restored to the previous mode. The effect of the min and time arguments is that any reads done on the terminal while raw mode is in effect will return to the caller after min bytes have been read or time deciseconds (1/10ths of a second) have elapsed, whichever comes first. Therefore, it makes no sense to use any read operation on the terminal exceptread-char
orread-string
, which read a fixed number of characters. No character is given special handling; all are passed to the application exactly as received. Echoing of input is disabled on the terminal during the execution ofproc
.In terms of the implementation model, the current terminal state is retrieved and a copy of it is pushed on the stack as a state object. Then the state object is modified by disabling the following flags:
ECHO ECHOE ECHOK ICANON IEXTEN ISIG BRKINT ICRNL INPCK ISTRIP IXON CSIZE PARENB OPOST
. TheCS8
setting is enabled, and theVMIN
andVTIME
settings are set using min and time respectively. Then the state object is written back to the terminal and the implementation model is followed thereafter.(with-raw-mode ((current-input-port) 2 50 (lambda (x y) (read-char) (read-char))) ⇒ #\x03
(with-rare-mode port proc)
→ [values] (procedure) POSIX tcgetattr(), tcsetattr()
The terminal is set to rare (also known as cbreak) mode during the dynamic execution of
proc
, and then is restored to the previous mode. Just as in canonical mode, any read operation on the terminal will wait until characters are received, unlike raw mode. However, no characters are given special interpretation except the characters that send signals (by default, Ctrl-C and Ctrl-\). Echoing of input is disabled on the terminal during the execution ofproc
.In terms of the implementation model, the current terminal state is retrieved and a copy of it is pushed on the stack as a state object. Then the state object is modified by disabling the
ECHO
,ECHOE
,ECHOK
andICANON
flags, and enabling theCS8
setting. Then the state object is written back to the terminal and the implementation model is followed thereafter.(with-rare-mode (current-input-port) (lambda (x y) (read-char))) ⇒ #\newline
(with-cooked-mode port proc)
→ [values] (procedure) POSIX tcgetattr(), tcsetattr()
The terminal is set to cooked mode during the dynamic execution of
proc
and then is restored to the previous mode. Echoing of input is enabled on the terminal during the execution ofproc
.In terms of the implementation model, the current terminal state is retrieved and a copy of it is pushed on the stack as a state object. Then a copy of the state object at the bottom of the stack is modified by enabling the following flags (which are probably already enabled, but we make sure)
ECHO ECHOE ECHOK ICANON IEXTEN ISIG BRKINT ICRNL INPCK ISTRIP IXON CSIZE PARENB OPOST
, and theCS8
setting is enabled. Then the modified state object is written back to the terminal and the implementation model is followed thereafter.(with-cooked-mode (current-input-port) (lambda (x y z) (<spawn-shell-process> x y z))) ⇒ 0
(without-echo port proc)
→ [values] (procedure) POSIX tcgetattr(), tcsetattr()
Echoing of input is disabled on the terminal during the execution of
proc
and then is re-enabled.In terms of the implementation model, the current terminal state is retrieved and a copy of it is pushed on the stack as a state object. Then the state object is modified by disabling the
ECHO
,ECHOE
andECHOK
flags, and enabling theCS8
setting. Then the state object is written back to the terminal and the implementation model is followed thereafter.(without-echo (current-input-port) (lambda (x) (read-string 8 (current-input-port)))) ⇒ "12345678"
(stty port . args)
→ undefined, list or state-object (procedure) POSIX tcgetattr(), tcsetattr()
Use in the manner of POSIX
stty(1)
, as extended to allow specifying the special device file to be manipulated, here associated with a port. When raising an error, the terminal port must be set back to its original values, and the error object must report which of the valuestcsetattr()
was unable to set is that is the cause. At least the following arguments must be supported, although they may or may not have the expected result:
Symbol arguments (arguments on the same line are mutually exclusive):
brkint
no-brkint
clocal
no-clocal
cmspar
no-cmspar
cread
no-cread
crtscts
no-crtscts
cs5
cs6
cs7
cs8
cstopb
no-cstopb
echo
no-echo
echoctl
no-echoctl
echoe
no-echoe
echok
no-echok
echonl
no-echonl
get
hup
no-hup
hupcl
no-hupcl
icanon
no-icanon
icrnl
no-icrnl
iexten
no-iexten
ignbrk
no-ignbrk
igncr
no-igncr
ignpar
no-ignpar
imaxbel
no-imaxbel
inlcr
no-inlcr
inpck
no-inpck
isig
no-isig
istrip
no-istrip
iuclc
no-iuclc
ixany
no-ixany
ixoff
no-ixoff
ixon
no-ixon
noflsh
no-noflsh
now
drain
flush
ocrnl
no-ocrnl
olcuc
no-olcuc
onlcr
no-onlcr
onlret
no-onlret
onocr
no-onocr
opost
no-opost
parenb
no-parenb
parmrk
no-parmrk
parodd
no-parodd
sane
state-object
tostop
no-tostop
xcase
no-xcase
The arguments
get
andsane
must be the only optional argument if they are used.get
returns a list of the current settings of the port as symbols and two-element lists suitable for using again withstty
.The argument
state-object
as a symbol causes stty to return an opaque object that was the state of the terminal prior to it being set. If the first element in a two element list, the terminal will be reset to the values of the second object, which must be astate-object
as previously returned by a call tostty
.Two-element list arguments of the form
(
symbol exact-integer)
where the symbol isispeed
orospeed
for reception and transmission speed, and the exact integer is the speed in bits per second, where 0 means "hang up". Ormin
ortime
for the minimum characters for a completed read and the read timeout in tenths of a second in non-canonical mode input processing.- Two-element list arguments of the form
(
symbol string)
where the symbol represents the system-independent name of a character:eof
,eol
,eol2
,erase
,intr
,kill
,lnext
,quit
,susp
,start
, orstop
. For the string:
- If a single character it is the actual character.
- If a two character sequence beginning with '
^
' it is treated as the control character for the second character, like "^c
" for U+0003.- If the string is "
^-
" or "undef
" the control character is disabled (set to_POSIX_VDISABLE
) if it is in effect for the device, if not an error is raised.
(get-possible-terminal-attributes)
→ list (procedure)
Returns a list of symbols and two-element lists that are acceptable to the implementation of
stty
. A particular serial line port may not support all of them.
See also the terminal?
procedure in
SRFI 170,
which returns #t
if the supplied port argument is a terminal.
(terminal-dimensions port)
→ list (procedure) POSIX ioctl()
Returns a list of two integers, the height and width of the terminal.
(terminal-file-name (current-output-port)) ⇒ (24 80)
(terminal-file-name port)
→ string (procedure) POSIX ttyname()
Returns the file name of the terminal.
(terminal-file-name port) ⇒ "/dev/ttyS0"
(terminal-flow-control port exact-integer)
→ unspecified (procedure) POSIX tcflow()
Controls the flow of characters in either direction, depending on which of
terminal/stop-output
,terminal/start-output
,terminal/stop-input
, andterminal/start-input
is the second argument(terminal-flow-control portterminal/start-output
) ⇒ unspecified
(terminal-wait port)
→ unspecified (procedure) POSIX tcdrain()
Waits until all characters queued to be sent on the terminal have been sent.
(terminal-wait port) ⇒ unspecified
(terminal-discard port int)
→ unspecified (procedure) POSIX tcflush()
Discards any input received but not yet read, or any output written but not yet sent, or both, depending on which of
terminal/discard-input
,terminal/discard-output
, orterminal/discard-both
is the second argument.(terminal-discard portdiscard-input
) ⇒ unspecified
(terminal-send-break port boolean)
→ undefined (procedure) POSIX tcsendbreak()
If boolean is false, sends a break signal (consecutive zero bits) for at least 0.25 seconds and not more than 0.5 seconds. If boolean is true, sends a break signal for an implementation-defined length of time.
(terminal-send-break port) ⇒ unspecified
The beginnings of a Chibi Scheme sample implementation can be found in srfi/chibi-scheme of the SRFI repository.
Alex Shinn conceived the operating paradigm for the
with-*
and without-echo
procedures,
which minimizes the classic risk of leaving a terminal in an
odd, often non-echoing state.
Copyright © (2022) John Cowan and Harold Ancell.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.