8

I'm using Term::ReadKey in ReadMode('cbreak') to read a single character and perform an action based on the input. This works fine for all other keys except the arrow keys. When the arrow keys are pressed, the action is performed 3 times and I understand this is because the arrow keys translate to '^[[A', etc...

How do I translate the arrow keys into some arbitrary single value that the ReadKey can interpret?

I tried the following code but it doesn't work:

use Term::ReadKey;

ReadMode('cbreak');

my $keystroke = '';

while ($keystroke ne 'h') {

    print "Enter key: "; 

    #Read user keystroke 
    $keystroke = ReadKey(0);

    chomp($keystroke);


    if(ord($keystroke) == 27) {
         $keystroke = ('0');
    }
}

Here's my code based on the suggestion:

use Term::RawInput;
use strict;
use warnings;

my $keystroke = '';
my $special = ''; 

while(lc($keystroke) ne 'i' && lc($keystroke) ne 't'){

    my $promptp = "Enter key: ";

    ($keystroke,$special) = rawInput($promptp, 1);

    if ($keystroke ne '') {
        print "You hit the normal '$keystroke' key\n";
    } else {
        print "You hit the special '$special' key\n";
    }

    chomp($keystroke);

    $keystroke = lc($keystroke);
}

if($keystroke eq 'i') {
    #Do something
}

if($keystroke eq 't') {
    #Do something
}

Now, no matter what I press, I can't exit this loop

Here's the output:

Enter key: 
Enter key:
Enter key: You hit the normal 't' key

#Proceeds to function
Chankey Pathak
  • 21,187
  • 12
  • 85
  • 133
user3821320
  • 137
  • 1
  • 9

4 Answers4

3

Here's my working solution...

use Term::ReadKey;

ReadMode('cbreak');

{
    #Temporarily turn off warnings so no messages appear for uninitialized $keystroke
    #that for some reason appears for the if statement
    no warnings;

    my $keystroke = '';

    while ($keystroke ne 'h') {

        print "\nEnter key: ";

        #Read user keystroke 
        $keystroke = ReadKey(0);

        #The first character for the arrow keys (ex. '^[[A') evaluates to 27 so I check for 
        #that
        if(ord($keystroke) == 27) {
            #Flush the rest of the characters from input buffer
            #This produces an 'Use of uninitialized value...' error
            #for the other two characters, hence 'no warnings' at the beginning.
            #This will ignore the other 2 characters and only cause a single iteration
            while( defined ReadKey(-1) ) {}
        }
    ReadMode 0;
    }
}
JohnE
  • 29,156
  • 8
  • 79
  • 109
user3821320
  • 137
  • 1
  • 9
  • Thank you! I've been searching all morning for a way to handle these special characters! – tjwrona1992 Sep 14 '15 at 17:13
  • Not sure why you use the `$mode` variable though. `while (defined ReadKey(-1)) {}` works fine for me. – tjwrona1992 Sep 14 '15 at 17:16
  • Good catch... The $mode variable served no useful meaning to this solution, it was a variable I needed for my program... I've updated the answer based on your input... Thanks @tjwrona1992 – user3821320 Sep 14 '15 at 21:38
  • Good to start, however I see some details: You are missing a `ReadMode 0` at the end of the code, so it will reset the mode. All arrow keys will return "27" as is the first in sequence. For example, UP is: 27,91,65 and DOWN is: 27,91,66, so for the code both are "27" (the rest discarded). The same happens with the [example at the documentation](http://search.cpan.org/~jstowe/TermReadKey-2.33/ReadKey.pm), so I guess is something with the library. – lepe Jul 26 '16 at 04:03
  • 1
    FYI, added the `ReadMode 0` as suggested by @lepe, which helps. This still doesn't work great but with that change it is a start at least – JohnE Apr 01 '21 at 15:01
2

Term::RawInput doesn't cover everything, but it's a pretty good start for this task:

use Term::RawInput;
my ($keystroke,$special) = rawInput("", 1);
if ($keystroke ne '') {
    print "You hit the normal '$keystroke' key\n";
} else {
    print "You hit the special '$special' key\n";
}
mob
  • 117,087
  • 18
  • 149
  • 283
  • This does accurately detect both the normal keys and special keys. However, now the same issue is occurring when I type the normal keys. For example, if I hit 't', my function executes 3 times, yet, if I hit the up arrow, my function correctly executes once. So I'm not sure how I'd use this in conjunction with ReadKeys to get a working solution. – user3821320 Sep 10 '15 at 00:43
  • @user3821320 what function? There isn't any in the code in your question. – hobbs Sep 10 '15 at 00:58
  • Sorry, I mean the prompt... the "Enter Key" prompt will display multiple times, even though I only hit the key once @hobbs – user3821320 Sep 10 '15 at 01:03
  • I've updated my question with the code from this suggestion... When I hit a regular key at the "Enter key: " prompt, the iteration will go through 3 times and on the last iteration it will display "You hit the normal ... key" @mob – user3821320 Sep 10 '15 at 01:35
  • `use strict; use warnings;`. The `$keystroke` in the `while` statement is different from the `my ($keystroke)` inside the loop. – mob Sep 10 '15 at 02:29
  • I've updated the code again. That did solve the issue of not existing the loop. However, it's still iterating 3 times on a single key press. @mob – user3821320 Sep 10 '15 at 04:03
  • This library detects UP / DOWN / LEFT / RIGHT arrows correctly. – lepe Jul 26 '16 at 04:07
2

If you're wanting to read high-level semantic ideas of "keypresses", rather than lower-level ideas of "bytes from the terminal" you'll need something that can parse and collect up those multi-byte sequences for you.

For this sort of task, I wrote Term::TermKey:

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 );
LeoNerd
  • 8,344
  • 1
  • 29
  • 36
  • I get an error when I try to install the module... "cpan Term::TermKey"... the first is "---- Unsatisfied dependencies detected during ---- ---- PEVANS/Term-TermKey-0.16.tar.gz ----" – user3821320 Sep 10 '15 at 14:21
  • 1
    @user3821320 stackoverflow isn't for support issues. Maybe try raising an RT ticket? https://rt.cpan.org/Dist/Display.html?Status=Active&Queue=Term-TermKey or email me - address is all over CPAN. – LeoNerd Sep 10 '15 at 16:28
1

Based on mob answer, using Term::RawInput, I did this script to emulate a more complex input interaction. The most important difference is the use of 'rawInput': mob suggested: rawInput("",1), I find out that actually using rawInput("> ") (without the second parameter) makes things easier to work with, and having a prompt ">" is more useful.

This code accepts commands and special keys. Also, it displays nicely to be used as an interactive shell for your system.

DELETE key will remove all chars and BACKSPACE a single character. ESC will exit the shell. You can add more keys to include arrows or anything else to perform special functions. This code will print-out any special key pressed which is not included inside the if..elsif (so you know what you need to add).

use warnings;
use strict;
use Term::RawInput;

sub out {
    my $out = shift;
    print "[ $out ]\n";
}

do {
    my ($keystroke,$special) = rawInput("> ");
    if($special eq 'ESC') {
        print "\n";
        exit;
    } elsif($special eq 'ENTER') {
        out($keystroke);
    } elsif($special ne 'DELETE') {
        if ($keystroke ne '') {
            out($keystroke);
        } else {
            print "'$special' key is not associated\n";
        }
    }
} while(1);

You can implement your commands inside "out";

lepe
  • 24,677
  • 9
  • 99
  • 108