The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Term::TermKey - perl wrapper around libtermkey

SYNOPSIS

 use Term::TermKey;

 my $tk = Term::TermKey->new( \*STDIN );

 print "Press any key\n";

 $tk->waitkey( my $key );

 print "You pressed: " . $tk->format_key( $key, 0 );

DESCRIPTION

This module provides a light perl wrapper around the libtermkey library. This library attempts to provide an abstract way to read keypress events in terminal-based programs by providing structures that describe keys, rather than simply returning raw bytes as read from the TTY device.

Multi-byte keys, ambiguous keys, and waittime

Some keypresses generate multiple bytes from the terminal. There is also the ambiguity between multi-byte CSI or SS3 sequences, and the Escape key itself. The waittime timer is used to distinguish them.

When some bytes arrive that could be the start of possibly multiple different keypress events, the library will attempt to wait for more bytes to arrive that would finish it. If no more bytes arrive after this time, then the bytes will be reported as events as they stand, even if this results in interpreting a partially-complete Escape sequence as a literal Escape key followed by some normal letters or other symbols.

Similarly, if the start of an incomplete UTF-8 sequence arrives when the library is in UTF-8 mode, this will be reported as the UTF-8 replacement character (U+FFFD) if it is incomplete after this time.

CONSTRUCTOR

$tk = Term::TermKey->new( $term, $flags )

Construct a new Term::TermKey object that wraps the given term handle. $term should be either an IO handle reference or an integer containing a plain POSIX file descriptor. $flags is optional, but if given, should contain the flags to pass to libtermkey's constructor. Assumes a default of 0 if not supplied. See the FLAG_* constants.

METHODS

$flags = $tk->get_flags

Return the current flags in operation, as specified in the constructor or the last call to set_flags(). One of the FLAG_UTF8 or FLAG_RAW flags will be set, even if neither was present in the constructor, as in this case the library will attempt to detect if the current locale is UTF-8 aware or not.

$tk->set_flags( $newflags )

Set the flags. This is a bitmask the same as the value passed to the constructor.

$msec = $tk->get_waittime

Return the current maximum wait time in miliseconds as set by the set_waittime() method. The underlying libtermkey library will have specified a default value when it started.

$tk->set_waittime( $msec )

Set the maximum wait time in miliseconds to await more of a partially complete key sequence.

$res = $tk->getkey( $key )

Attempt to retrieve a single keypress event from the buffer, and put it in $key. If successful, will return RES_KEY to indicate that the $key structure now contains a new keypress event. If $key is an undefined lvalue (such as a new scalar variable) it will be initialised to contain a new key structure.

If nothing is in the buffer it will return RES_NONE. If the buffer contains a partial keypress event which does not yet contain all the bytes required, it will return RES_AGAIN (see above section about multibyte events). If no events are ready and the input stream is now closed, will return RES_EOF.

This method will not block, nor will it perform any IO on the underlying file descriptor. For a normal blocking read, see waitkey().

$res = $tk->getkey_force( $key )

Similar to getkey(), but will not return RES_AGAIN if a partial match was found. Instead, it will force an interpretation of the bytes, even if this means interpreting the start of an <Esc>-prefixed multibyte sequence as a literal Escape key followed by normal letters. If $key is an undefined lvalue (such as a new scalar variable) it will be initialised to contain a new key structure.

This method will not block, nor will it perform any IO on the underlying file descriptor. For a normal blocking read, see waitkey().

$res = $tk->waitkey( $key )

Attempt to retrieve a single keypress event from the buffer, or block until one is available. If successful, will return RES_KEY to indicate that the $key structure now contains a new keypress event. The only other result it can return is RES_EOF, to indicate that the input stream is now closed. If $key is an undefined lvalue (such as a new scalar variable) it will be initialised to contain a new key structure.

$res = $tk->advisereadable

Inform the underlying library that new input may be available on the underlying file descriptor and so it should call read() to obtain it. Will return RES_AGAIN if it read at least one more byte, or RES_NONE if no more input was found.

Normally this method would only be used in programs that want to use Term::TermKey asynchronously; see the EXAMPLES section. This method gracefully handles an EAGAIN error from the underlying read() syscall.

$str = $tk->get_keyname( $sym )

Returns the name of a key sym, such as returned by Term::TermKey::Key->sym().

$sym = $tk->keyname2sym( $keyname )

Look up the sym for a named key. The result of this method call can be compared directly against the value returned by Term::TermKey::Key->sym(). Because this method has to perform a linear search of key names, it is best called rarely, perhaps during program initialisation, and the result stored for easier comparisons during runtime.

$str = $tk->format_key( $key, $format )

Return a string representation of the keypress event in $key, following the flags given. See the descriptions of the flags, below, for more detail.

This may be useful for matching keypress events against keybindings stored in a hash. See EXAMPLES section for more detail.

KEY OBJECTS

The Term::TermKey::Key subclass is used to store a single keypress event. Objects in this class cannot be changed by perl code. getkey(), getkey_force() or waitkey() will overwrite the contents of the structure with a new value.

$key = Term::TermKey::Key->new

Construct a new blank key event structure.

$key->type

The type of event. One of TYPE_UNICODE, TYPE_FUNCTION, TYPE_KEYSYM.

$key->type_is_unicode

$key->type_is_function

$key->type_is_keysym

Shortcuts which return a boolean.

$key->codepoint

The Unicode codepoint number for TYPE_UNICODE, or 0 otherwise.

$key->number

The function key number for TYPE_FUNCTION, or 0 otherwise.

$key->sym

The key symbol number for TYPE_KEYSYM, or 0 otherwise. This can be passed to Term::TermKey->get_keyname(), or compared to a result earlier obtained from Term::TermKey->keyname2sym().

$key->modifiers

The modifier bitmask. Can be compared against the KEYMOD_* constants.

$key->utf8

A string representation of the given Unicode codepoint. If the underlying termkey library is in UTF-8 mode then this will be a UTF-8 string. If it is in raw mode, then this will be a single raw byte.

EXPORTED CONSTANTS

The following constant names are all derived from the underlying libtermkey library. For more detail see the documentation on the library.

These constants are possible values of $key->type

TYPE_UNICODE

a Unicode codepoint

TYPE_FUNCTION

a numbered function key

TYPE_KEYSYM

a symbolic key

These constants are result values from getkey(), getkey_force(), waitkey() or advisereadable()

RES_NONE

No key event is ready.

RES_KEY

A key event has been provided.

RES_EOF

No key events are ready and the terminal has been closed, so no more will arrive.

RES_AGAIN

No key event is ready yet, but a partial one has been found. This is only returned by getkey(). To obtain the partial result even if it never completes, call getkey_force().

These constants are key modifier masks for $key->modifiers

KEYMOD_SHIFT
KEYMOD_ALT
KEYMOD_CTRL

Should be obvious ;)

These constants are flags for the constructor, Term::TermKey->new

FLAG_NOINTERPRET

Do not attempt to interpret C0 codes into keysyms (ie. Backspace, Tab, Enter, Escape). Instead report them as plain Ctrl-letter events.

FLAG_CONVERTKP

Convert xterm's alternate keypad symbols into the plain ASCII codes they would represent.

FLAG_RAW

Ignore locale settings; do not attempt to recombine UTF-8 sequences. Instead report only raw values.

FLAG_UTF8

Ignore locale settings; force UTF-8 recombining on.

FLAG_NOTERMIOS

Even if the terminal file descriptor represents a TTY device, do not call the tcsetattr() termios function on it to set in canonical input mode.

These constants are flags to format_key

FORMAT_LONGMOD

Print full modifier names e.g. Shift- instead of abbreviating to S-.

FORMAT_CARETCTRL

If the only modifier is Ctrl on a plain character, render it as ^X.

FORMAT_ALTISMETA

Use the name Meta or the letter M instead of Alt or A.

FORMAT_WRAPBRACKET

If the key event is a special key instead of unmodified Unicode, wrap it in <brackets>.

FORMAT_VIM

Shortcut to FORMAT_ALTISMETA|FORMAT_WRAPBRACKET; which gives an output close to the format the vim editor uses.

EXAMPLES

A simple print-until-Ctrl-C loop

This program just prints every keypress until the user presses Ctrl-C.

 use Term::TermKey qw( FLAG_UTF8 RES_EOF KEYMOD_CTRL FORMAT_VIM );
 
 my $tk = Term::TermKey->new(\*STDIN);
 
 # ensure perl and libtermkey agree on Unicode handling
 binmode( STDOUT, ":utf8" ) if $tk->get_flags & FLAG_UTF8;
 
 while( ( my $ret = $tk->waitkey( my $key ) ) != RES_EOF ) {
    print "Got key: ".$tk->format_key( $key, FORMAT_VIM )."\n";

    last if $key->type_is_unicode and 
            lc $key->utf8 eq "c" and
            $key->modifiers & KEYMOD_CTRL;
 }

Configuration of custom keypresses

Because format_key() yields a plain string representation of a keypress it can be used as a hash key to look up a "handler" routine for the key.

The following implements a simple line input program, though obviously lacking many features in a true line editor like readline.

 use Term::TermKey qw( FLAG_UTF8 RES_EOF FORMAT_LONGMOD );
 
 my $tk = Term::TermKey->new(\*STDIN);
 
 # ensure perl and libtermkey agree on Unicode handling
 binmode( STDOUT, ":utf8" ) if $tk->get_flags & FLAG_UTF8;

 my $line = "";

 $| = 1;

 my %key_handlers = (
    "Ctrl-c" => sub { exit 0 },

    "Enter"  => sub { 
       print "\nThe line is: $line\n";
       $line = "";
    },

    "Backspace" => sub {
       return unless length $line;
       substr( $line, -1, 1 ) = "";
       print "\cH \cH"; # erase it
    },

    "Space" => sub {
       $line .= " ";
       print " ";
    },

    # other handlers ...
 );
 
 while( ( my $ret = $tk->waitkey( my $key ) ) != RES_EOF ) {
    my $handler = $key_handlers{ $tk->format_key( $key, FORMAT_LONGMOD ) };
    if( $handler ) {
       $handler->( $key );
    }
    elsif( $key->type_is_unicode and !$key->modifiers ) {
       my $char = $key->utf8;

       $line .= $char;
       print $char;
    }
 }

Asynchronous operation

Because the getkey() method performs no IO itself, it can be combined with the advisereadable() method in an asynchronous program.

 use IO::Select;
 use Term::TermKey qw(
    FLAG_UTF8 KEYMOD_CTRL RES_KEY RES_AGAIN RES_EOF FORMAT_VIM
 );
 
 my $select = IO::Select->new();
 
 my $tk = Term::TermKey->new(\*STDIN);
 $select->add(\*STDIN);
 
 # ensure perl and libtermkey agree on Unicode handling
 binmode( STDOUT, ":utf8" ) if $tk->get_flags & FLAG_UTF8;
 
 sub on_key
 {
    my ( $tk, $key ) = @_;
 
    print "You pressed " . $tk->format_key( $key, FORMAT_VIM ) . "\n";
 
    exit if $key->type_is_unicode and
            lc $key->utf8 eq "c" and
            $key->modifiers & KEYMOD_CTRL;
 }
 
 my $again = 0;
 
 while(1) {
    my $timeout = $again ? $tk->get_waittime/1000 : undef;
    my @ready = $select->can_read($timeout);
 
    if( !@ready ) {
       my $ret;
       while( ( $ret = $tk->getkey_force( my $key ) ) == RES_KEY ) {
          on_key( $tk, $key );
       }
    }
 
    while( my $fh = shift @ready ) {
       if( $fh == \*STDIN ) {
          $tk->advisereadable;
          my $ret;
          while( ( $ret = $tk->getkey( my $key ) ) == RES_KEY ) {
             on_key( $tk, $key );
          }
 
          $again = ( $ret == RES_AGAIN );
          exit if $ret == RES_EOF;
       }
       # Deal with other filehandles here
    }
 }

See also the Term::TermKey::Async module which provides a convenient wrapping of Term::TermKey for an IO::Async-based program.

TODO

  • Consider if $key = $tk->waitkey is a better API. While underlying library only returns RES_KEY or RES_NONE that works but if it ever gains another value, all bets are off. Return undef and have a ->err method? Going into messyland...

SEE ALSO

AUTHOR

Paul Evans <leonerd@leonerd.org.uk>