NAME
Linux::Event::IO::TTY - Asynchronous terminal and pseudo-terminal I/O
SYNOPSIS
use v5.36;
use Linux::Event::Loop;
use Linux::Event::IO::TTY;
my $loop = Linux::Event::Loop->new;
my $tty = Linux::Event::IO::TTY->new(
loop => $loop,
read_fh => \*STDIN,
write_fh => \*STDOUT,
on_data => sub ($self, $bytes) {
$self->write("You typed: $bytes");
},
);
$loop->run;
DESCRIPTION
Linux::Event::IO::TTY provides asynchronous byte I/O for terminals and pseudo-terminals.
It can be used for things such as:
interactive terminal input and output
STDINandSTDOUTpseudo-terminals used to communicate with subprocesses
terminal devices opened by an application
TTY uses the same ordered-byte I/O engine as Linux::Event::IO::Pipe and Linux::Event::IO::Sock::Stream.
That means it supports:
raw
on_datacallbacksLinux::Event::Framer message framing
queued asynchronous output
backpressure
read pausing
timeouts and deadlines
LINUX::EVENT DOES NOT CONFIGURE TERMINAL MODE
Linux::Event::IO::TTY handles asynchronous I/O.
It does not automatically change terminal behavior.
In particular, creating a TTY object does not automatically:
enable or disable canonical mode
enable or disable echo
put the terminal into raw mode
change baud rates
change character-processing flags
restore previous terminal settings later
Those settings are controlled separately through the terminal's termios configuration.
For example, if STDIN is still in normal canonical terminal mode, the kernel may continue collecting input until the user presses Enter before Linux::Event receives it.
If an application wants individual key presses, raw mode, disabled echo, or other terminal behavior, it must configure those settings separately.
Linux::Event then asynchronously reads and writes whatever byte behavior that terminal mode provides.
CREATING A TERMINAL OBJECT
A common interactive terminal uses separate input and output handles:
my $tty = Linux::Event::IO::TTY->new(
loop => $loop,
read_fh => \*STDIN,
write_fh => \*STDOUT,
on_data => sub ($self, $bytes) {
...
},
);
Both supplied handles must be terminals or pseudo-terminals according to Perl's -t test.
HANDLE OWNERSHIP
TTY handles are borrowed by default.
This is deliberate because the most common terminal handles are often the program's own STDIN and STDOUT:
my $tty = Linux::Event::IO::TTY->new(
read_fh => \*STDIN,
write_fh => \*STDOUT,
...
);
Linux::Event temporarily makes borrowed handles nonblocking and close-on-exec while the TTY is managing them.
When the TTY becomes terminal through close, Linux::Event stops using the borrowed handles, leaves them open, and restores the file status and descriptor flags that were present when the TTY was constructed.
Therefore this is valid:
my $tty = Linux::Event::IO::TTY->new(
loop => $loop,
read_fh => \*STDIN,
write_fh => \*STDOUT,
...
);
...
$tty->close;
say "ordinary STDOUT still works";
Closing the TTY does not close STDIN or STDOUT in the default borrowed mode.
While borrowed handles are active
While Linux::Event is managing a borrowed terminal handle, that descriptor is nonblocking.
On Linux, O_NONBLOCK belongs to the underlying open-file description. Other file descriptors duplicated from the same terminal open-file description may therefore observe the nonblocking setting while the TTY is active.
For example, ordinary buffered print to STDOUT should not be mixed casually with asynchronous $tty->write(...) output while Linux::Event is actively managing that same terminal.
Use the TTY's write or send methods for output during the managed lifetime. After the borrowed TTY closes or detaches, Linux::Event restores the captured descriptor flags and ordinary Perl I/O can resume normally.
owns_handles
An application may explicitly transfer handle ownership to the TTY:
my $tty = Linux::Event::IO::TTY->new(
fh => $terminal,
owns_handles => 1,
...
);
With owns_handles => 1, normal TTY close operations own and close the supplied handles.
The default is:
owns_handles => 0
Use owning mode for terminal handles whose lifetime should be controlled entirely by the TTY object rather than by the surrounding application.
owns_handles()
if ($tty->owns_handles) {
...
}
Return true when the TTY was constructed with owns_handles => 1.
READ-ONLY TERMINALS
Supply only read_fh when Linux::Event should read from a terminal:
my $tty = Linux::Event::IO::TTY->new(
loop => $loop,
read_fh => \*STDIN,
on_data => sub ($self, $bytes) {
print "Received: $bytes";
},
);
The TTY has no writable direction in this form.
WRITE-ONLY TERMINALS
Supply only write_fh when Linux::Event should write to a terminal:
my $tty = Linux::Event::IO::TTY->new(
loop => $loop,
write_fh => \*STDOUT,
);
Then:
$tty->write("hello\n");
sends output asynchronously.
No input callback is required because there is no readable side.
SEPARATE READ AND WRITE HANDLES
A TTY may combine two different terminal handles into one logical object:
my $tty = Linux::Event::IO::TTY->new(
loop => $loop,
read_fh => $input,
write_fh => $output,
on_data => sub ($self, $bytes) {
...
},
);
This is useful with STDIN/STDOUT and with PTY arrangements where input and output use different descriptors.
ONE HANDLE FOR BOTH DIRECTIONS
If one terminal handle is both readable and writable, use fh:
my $tty = Linux::Event::IO::TTY->new(
loop => $loop,
fh => $terminal,
on_data => sub ($self, $bytes) {
...
},
);
fh cannot be combined with read_fh or write_fh.
ATTACHING TO A LOOP
A TTY may be attached during construction:
my $tty = Linux::Event::IO::TTY->new(
loop => $loop,
read_fh => \*STDIN,
...
);
or constructed first:
my $tty = Linux::Event::IO::TTY->new(
read_fh => \*STDIN,
...
);
and attached later:
$loop->add($tty);
RECEIVING INPUT
on_data
An unframed readable TTY receives bytes through on_data:
on_data => sub ($self, $bytes) {
...
}
$bytes contains the next available part of the terminal byte stream.
The exact shape of that input depends partly on the terminal mode.
For example, a terminal in canonical mode commonly performs line discipline before Linux::Event sees the data.
A terminal in raw mode may make individual bytes available much sooner.
Linux::Event does not assume either behavior.
LINE-ORIENTED INPUT
A terminal protocol can use Linux::Event::Framer just like a Stream or Pipe.
For example:
package Console;
use parent 'Linux::Event::IO::TTY';
use Linux::Event::Framer 'Delimiter', "\n";
sub on_message ($self, $line) {
$self->write("You typed: $line\n");
}
Then:
my $console = Console->new(
loop => $loop,
read_fh => \*STDIN,
write_fh => \*STDOUT,
);
The framer operates on the bytes delivered by the terminal.
It does not alter the terminal's own line discipline.
Constructor callback with framing
A framed subclass may still receive its message callback through the constructor:
my $prefix = 'input';
my $console = Console->new(
loop => $loop,
read_fh => \*STDIN,
write_fh => \*STDOUT,
on_message => sub ($self, $line) {
say "$prefix: $line";
},
);
The constructor callback overrides a same-named subclass method for that TTY.
WRITING OUTPUT
write($bytes)
$tty->write("hello\n");
Send raw bytes to the writable terminal handle.
Linux::Event first attempts to write immediately.
If the terminal cannot accept all the data at once, remaining bytes are queued and written later.
Output order is preserved.
send($payload)
For a framed TTY:
$tty->send($payload);
send applies the subclass's Linux::Event::Framer before writing.
For an unframed TTY, use write.
BACKPRESSURE
TTY output uses the same high- and low-watermark backpressure system as other ordered-byte resources.
When queued output grows past the high watermark, write or send begins returning false.
The data is still accepted unless a hard output limit would be exceeded.
When queued data later falls to the low watermark, on_drain is called:
on_drain => sub ($self) {
# Producing more output is safe again.
}
EOF
on_eof
on_eof => sub ($self) {
say "Terminal input reached EOF";
}
Called when the readable direction reaches EOF.
For an interactive terminal this may happen, for example, when the terminal or PTY peer is closed or when terminal input produces an EOF condition.
The writable direction may still exist independently.
PAUSING INPUT
pause_read
$tty->pause_read;
Temporarily stop application input delivery.
resume_read
$tty->resume_read;
Resume input delivery.
A configured read timeout is suspended while input is deliberately paused.
CALLBACKS
TTY callbacks may be constructor coderefs or subclass methods.
The normal callbacks are:
on_data($tty, $bytes)-
Raw unframed input arrived.
on_message($tty, $message)-
One complete framed message arrived.
on_messages($tty, $messages)-
A batch of framed messages arrived when message batching is enabled.
on_drain($tty)-
Queued output fell to the low watermark after backpressure.
on_eof($tty)-
The readable direction reached EOF.
on_error($tty, $error)-
An asynchronous I/O, framing, timeout, or queue-limit error occurred.
on_close($tty)-
The TTY closed.
A constructor callback overrides a same-named subclass method for that object.
APPLICATION DATA
Arbitrary application state may be attached to the TTY:
my $tty = Linux::Event::IO::TTY->new(
read_fh => \*STDIN,
data => $state,
...
);
and retrieved through:
my $state = $tty->data;
CLOSING DIRECTIONS
A TTY with separate read and write directions can control them independently.
In the default borrowed-handle mode, directional close stops Linux::Event from using that direction but does not close the caller's terminal handle.
If another direction remains active, restoration of borrowed descriptor flags is deferred until the complete TTY becomes terminal. This avoids changing descriptor state out from under the still-active direction.
With owns_handles => 1, released distinct directional handles are closed as part of the owning lifecycle.
close_read
$tty->close_read;
Stop the readable direction immediately.
The writable direction may remain active.
close_write
$tty->close_write;
Stop the writable direction immediately.
The readable direction may remain active.
end
$tty->end;
Allow already accepted output to drain and then end the writable direction.
Use this when pending output should finish before the writable side ends.
close
$tty->close;
Make the whole TTY terminal immediately.
For the default borrowed TTY, close leaves the supplied handles open and restores their captured file status and descriptor flags.
For owns_handles => 1, close closes the owned handles.
DETACHING TERMINAL HANDLES
detach
my $handles = $tty->detach;
Stop Linux::Event management and return the still-open terminal handles.
The return value is a hash reference containing:
read_fh
write_fh
as applicable.
For example:
my $handles = $tty->detach;
my $input = $handles->{read_fh};
my $output = $handles->{write_fh};
Detachment requires the output queue to be empty.
It is terminal for the TTY object and does not call on_close.
For the default borrowed TTY, detach restores the file status and descriptor flags captured at construction before returning the handles.
For owns_handles => 1, detach transfers ownership of the still-open handles to the caller. Those owned handles retain their current nonblocking and close-on-exec descriptor configuration.
Neither close nor detach changes terminal mode or restores termios state, because Linux::Event did not configure termios state in the first place.
SUBCLASSING
Subclassing is optional.
Constructor callbacks are often simplest for one terminal:
my $tty = Linux::Event::IO::TTY->new(
read_fh => \*STDIN,
on_data => sub ($self, $bytes) {
...
},
);
A subclass is useful when many TTY objects share framing, callbacks, or tuning:
package CommandConsole;
use parent 'Linux::Event::IO::TTY';
use Linux::Event::Framer 'Delimiter', "\n";
sub on_message ($self, $command) {
...
}
STREAM TUNING
TTY uses the common ordered-byte tuning model.
Most applications should leave the defaults unchanged.
Reusable defaults may be declared by a subclass with stream_tuning:
package InteractiveTTY;
use parent 'Linux::Event::IO::TTY';
sub stream_tuning ($class) {
return (
read_size => 16_384,
read_budget_bytes => 65_536,
max_buffer => 1_048_576,
);
}
stream_tuning is subclass policy.
The values below do not go inside a constructor tuning hash.
The timeout values idle_timeout, read_timeout, and write_timeout may also be overridden directly for one TTY in new.
read_size
Default: 65,536 bytes.
Maximum number of bytes requested by one native read.
read_budget_bytes
Default: 65,536 bytes.
Maximum amount of input processed during one readiness turn before yielding to other Loop resources.
This is a fairness control.
A value of zero means to continue reading until the descriptor would block.
read_batch_bytes
Default: 0.
For an unframed TTY, successful reads may be combined before on_data is called.
Zero preserves normal read callback boundaries.
This option cannot be used with framing.
message_batch_size
Default: 0.
For a framed TTY, deliver up to this many complete messages together through on_messages.
Zero uses normal on_message delivery.
A positive value requires framing and an on_messages callback.
max_buffer
Default: 8,388,608 bytes.
Hard limit for retained input, incomplete framing data, and retained message batch data.
high_watermark
Default: 1,048,576 bytes.
Queued-output level at which write and send begin returning false to signal backpressure.
low_watermark
Default: 262,144 bytes.
After backpressure has occurred, on_drain fires when queued output falls to or below this level.
The low watermark cannot exceed the high watermark.
max_pending_bytes
Default: 0.
Hard limit on queued output bytes.
Zero means no hard output-queue limit.
idle_timeout
Default: 0.
Maximum number of seconds without successful input or output progress.
Zero disables the timeout.
read_timeout
Default: 0.
Maximum number of seconds without inbound progress while reading is active.
pause_read suspends this timeout.
Zero disables it.
write_timeout
Default: 0.
Maximum number of seconds without output progress while data remains queued.
Zero disables it.
PER-OBJECT TIMEOUTS AND DEADLINES
Timeout overrides for one TTY are top-level constructor options:
my $tty = Linux::Event::IO::TTY->new(
loop => $loop,
read_fh => \*STDIN,
idle_timeout => 300,
read_timeout => 60,
on_data => sub ($self, $bytes) {
...
},
);
An explicit operation deadline is also a top-level new option, but its value is a hash describing the deadline:
my $tty = Linux::Event::IO::TTY->new(
loop => $loop,
read_fh => \*STDIN,
deadline => {
after => 30,
operation => 'initial_input',
},
on_data => sub ($self, $bytes) {
...
},
);
A deadline requires exactly one of after or at, plus a non-empty operation name.
These settings do not go inside a nested tuning hash.
See docs/ORDERED-BYTE-DEADLINES.md for the full deadline model.
CHANGING TUNING AT RUNTIME
tune
A live TTY may change its mutable ordered-byte policy:
$tty->tune(
read_budget_bytes => 131_072,
idle_timeout => 120,
);
tune supports the same mutable ordered-byte settings used by Stream and Pipe.
It returns the TTY object.
Tuning does not change terminal mode or termios configuration.
INFORMATION METHODS
fh
Return the shared handle when the same descriptor supplies both reading and writing.
If separate descriptors are used, fh returns undef.
read_fh
Return the readable terminal handle when present.
write_fh
Return the writable terminal handle when present.
read_fd
Return the readable file descriptor when present.
write_fd
Return the writable file descriptor when present.
has_read
Return true when the TTY has a readable direction.
has_write
Return true when the TTY has a writable direction.
pending_bytes
Return the number of bytes currently queued for output.
state
Return the current TTY lifecycle state.
is_read_paused
Return true while application reading is paused.
is_read_eof
Return true after the readable direction reaches EOF.
is_read_closed
Return true after the readable direction has been closed.
is_write_ended
Return true after the writable direction has ended.
last_error
Return the most recently stored Linux::Event::Error, when one exists.
PERFORMANCE MODEL
TTY uses Linux::Event's native ordered-byte engine.
Callbacks and reusable subclass policy are resolved when the object is created, rather than repeatedly looked up for every terminal readiness event.
Framing, buffering, output queuing, and backpressure are handled before control returns to application callbacks.
These details normally require no application action.
SEE ALSO
Linux::Event, Linux::Event::Loop, Linux::Event::IO::Pipe, Linux::Event::IO::Sock::Stream, Linux::Event::Framer, Linux::Event::Error, docs/ORDERED-BYTE-IO-DESIGN.md, docs/ORDERED-BYTE-DEADLINES.md.