Safe way to establish the initial state again. - perl

Is there a possibility of calling the function main without the function _end_win being called?
#!/usr/bin/env perl
use warnings;
use strict;
use 5.10.0;
use Term::ReadKey;
use constant {
NEXT_getch => -1,
CONTROL_C => 0x03,
CONTROL_D => 0x04,
KEY_ENTER => 0x0d,
KEY_RIGHT => 0x1b5b43,
KEY_LEFT => 0x1b5b44,
};
say main();
sub main {
my $arg = {};
$arg->{handle_out} = *STDOUT;
_init_scr( $arg );
while ( 1 ) {
my $c = _getch( $arg );
if ( ! defined $c ) {
_end_win( $arg );
warn "EOT";
return;
}
next if $c == NEXT_getch;
given ( $c ) {
when ( $c >= 97 && $c <= 122 ) {
print chr $c;
$arg->{string} .= chr $c;
}
when ( $c == KEY_RIGHT ) {
print '>';
$arg->{string} .= '>';
}
when ( $c == KEY_LEFT ) {
print '<';
$arg->{string} .= '<';
}
when ( $c == CONTROL_D ) {
_end_win( $arg );
return;
}
when ( $c == CONTROL_C ) {
_end_win( $arg );
print STDERR "^C";
kill( 'INT', $$ );
return;
}
when ( $c == KEY_ENTER ) {
_end_win( $arg );
return $arg->{string};
}
}
}
}
sub _init_scr {
my ( $arg ) = #_;
$arg->{old_handle} = select( $arg->{handle_out} );
$arg->{backup_flush} = $|;
$| = 1;
Term::ReadKey::ReadMode 'ultra-raw';
}
sub _end_win {
my ( $arg ) = #_;
print "\n\r";
Term::ReadKey::ReadMode 'restore';
$| = $arg->{backup_flush};
select( $arg->{old_handle} );
}
sub _getch {
my ( $arg ) = #_;
my $c1 = ReadKey 0;
return if ! defined $c1;
if ( $c1 eq "\e" ) {
my $c2 = ReadKey 0.10;
if ( ! defined $c2 ) { return NEXT_getch; }
elsif ( $c2 eq 'C' ) { return KEY_RIGHT; }
elsif ( $c2 eq 'D' ) { return KEY_LEFT; }
elsif ( $c2 eq '[' ) {
my $c3 = ReadKey 0;
if ( $c3 eq 'C' ) { return KEY_RIGHT; }
elsif ( $c3 eq 'D' ) { return KEY_LEFT; }
else {
return NEXT_getch;
}
}
else {
return NEXT_getch;
}
}
else {
return ord $c1;
}
}

To ensure that the terminal is reset when your program exits, put the reset code into an END block. For example, you could replace your _end_win sub with:
END {
print "\n\r";
Term::ReadKey::ReadMode 'restore';
}
(I removed the lines resetting $| and the selected output filehandle since the process is exiting anyhow, so they're about to become irrelevant.)
An END block will always run when the program terminates in a "normal" way, such as calling exit or die or hitting the end of the executable code. It does not fire when the process terminates due to receiving a signal; it looks like you're handling the ctrl-C character directly, but you may want to consider adding a %SIG{INT} handler as well, in case someone sends you a kill -2.

Related

Custom Storable hooks for dclone-ing a light-weight object referencing a heavy-weight object

Say I have a tiny object that has a reference to a huge object:
package Tiny;
sub new {
my ($class, $tiny, $large) = #_;
return bless { tiny => $tiny, large => $large };
}
I'd like to create a STORABLE_freeze/STORABLE_thaw pair that lets me (recursively) clone $tiny but maintain/keep the reference to $large as-is without cloning $large too.
I tried temporarily deleting $self->{large} (see below), and putting it in a hash with a Scalar::Util::refaddr key and a weak reference to $large, serializing the rest of $self, and then putting the (weak) reference back into both the original object immediately and the cloned one in STORABLE_thaw, but it is a mess, and on every clone, the weak ref value gets deleted when it goes out of scope, but the key remains in the hash forever leaking memory and I need a global class member hash (%largeWeakRefs) to hold the temporary $large reference. Has a smell.
How it that possible to do this in a cleaner way?
Here is my solution using the hash to hold the large ref temporarily:
package Tiny;
use Scalar::Util qw(refaddr weaken);
sub new {
my ( $class, $tiny, $large ) = #_;
return bless { tiny => $tiny, large => $large }, $class;
}
# Ugly temporary storage to hold $large refs from _freeze to _thaw...
my %largeWeakRefs;
sub STORABLE_freeze {
my ( $self, $cloning ) = #_;
my $large = delete local $self->{large};
my $refaddr = refaddr $large;
$largeWeakRefs{$refaddr} = $large;
weaken $largeWeakRefs{$refaddr};
my %restOfSelf = %$self;
$self->{large} = $large;
return $refaddr, \%restOfSelf;
}
sub STORABLE_thaw {
my ($self, $cloning, $refaddr, $restOfSelf) = #_;
%$self = %$restOfSelf;
$self->{large} = $largeWeakRefs{$refaddr};
return $self;
}
(Yes I know, my example only handles cloning, not straight-up freeze and thaw)
You could add reference counts.
my %larges;
sub STORABLE_freeze {
my ( $self, $cloning ) = #_;
if ($cloning) {
my $large_key = pack('j', refaddr(self->{large}));
$larges{$large_key} //= [ $self->{large}, 0 ];
++$larges{$large_key}[1];
return ( $large_key, $self->{tiny} );
} else {
return ( "", $self->{tiny}, $self->{large} );
}
}
sub STORABLE_thaw {
my ( $self, $cloning, $serialized ) = splice(#_, 0, 3);
if ($cloning) {
my $large_key = $serialized;
$self->{ tiny } = shift;
$self->{ large } = $larges{$large_key}[0];
--$larges{$large_key}[1]
or delete($larges{$large_key});
} else {
$self->{ tiny } = shift;
$self->{ large } = shift;
}
}
Untested.
If the cloning process dies, you'll have a memory leak.
Alternatively, you could avoid the need for external resources as follows:
use Inline C => <<'__EOS__';
IV get_numeric_ref(SV *sv) {
SvGETMAGIC(sv);
if (!SvROK(sv))
croak("Argument not a reference");
sv = MUTABLE_SV(SvRV(sv));
SvREFCNT_inc(sv);
return PTR2IV(sv); /* Despite its name, can be used to convert pointer to IV */
}
SV* get_perl_ref_from_numeric_ref(IV iv) {
SV* sv = PTR2IV(iv);
return newRV_noinc(sv);
}
__EOS__
sub STORABLE_freeze {
my ( $self, $cloning ) = #_;
if ($cloning) {
return ( pack('j', get_numeric_ref($self->{large})), $self->{tiny} );
} else {
return ( "", $self->{tiny}, $self->{large} );
}
}
sub STORABLE_thaw {
my ( $self, $cloning, $serialized ) = splice(#_, 0, 3);
if ($cloning) {
$self->{ tiny } = shift;
$self->{ large } = get_perl_ref_from_numeric_ref(unpack('j', $serialized));
} else {
$self->{ tiny } = shift;
$self->{ large } = shift;
}
}
Didn't test STORABLE_freeze and STORABLE_thaw, but tested the C/XS code using the following:
use strict;
use warnings;
use feature qw( say state );
use Cpanel::JSON::XS qw( );
sub _dump {
state $encoder = Cpanel::JSON::XS->new->canonical->allow_nonref;
return $encoder->encode($_[0]);
}
{
my %h = ( a => 4, b => 5 );
say _dump(\%h); # {"a":4,"b":5}
say sprintf "0x%x", \%h; # 0x32cdbf8
say Internals::SvREFCNT(%h); # 1
my $i = get_numeric_ref(\%h);
say sprintf "0x%x", $i; # 0x32cdbf8
say Internals::SvREFCNT(%h); # 2
my $ref = get_perl_ref_from_numeric_ref($i);
say sprintf "0x%x", $ref; # 0x32cdbf8
say Internals::SvREFCNT(%h); # 2
say _dump($ref); # {"a":4,"b":5}
}
If the cloning process dies, you'll have a memory leak. I suppose it would be safe to rely on "large" not going anywhere during the cloning process, so you could remove the SvREFCNT_inc and change newRV_noinc to newRV to avoid the potential memory leak.
To avoid the possible memory leak, never store "large" in the object.
my %larges;
sub new {
my $class = shift;
my $self = bless({}, $class);
return $self->_init(#_);
}
sub _init {
my ($self, $tiny, $large) = #_;
$self->{ tiny } = $tiny;
{
my $large_key = pack('j', refaddr($self));
$self->{ large_key } = $large_key;
$larges{ $large_key } = $large;
}
return $self;
}
sub DESTROY {
my ($self) = #_;
if (defined( my $large_key = $self->{ large_key } )) {
delete( $larges{ $large_key } );
}
}
sub STORABLE_freeze {
my ( $self, $cloning ) = #_;
if ($cloning) {
return ( $self->{large_key}, $self->{tiny} );
} else {
return ( "", $self->{tiny}, $larges{ $self->{large_key} } );
}
}
sub STORABLE_thaw {
my ( $self, $cloning, $serialized ) = splice(#_, 0, 3);
if ($cloning) {
my ($tiny) = #_;
my $large_key = $serialized;
$self->_init($tiny, $larges{ $large_key });
} else {
$self->_init(#_);
}
}
Untested.
No memory leaks if the cloning process dies.

perl script with AnyEvent is not going fast enough

Here is the little script I created to do HTTP transactions from a file with a list of URLs. The problem is that it's not able to do HTTP transactions as fast as I would like. More precisely, I set the rate to be 200/seconds, but it was able to send only at about 50/second. The server is powerful enough to handle 100/second.
This was run on a powerful PC with E5-1650 CPU and 64GB of RAM running Ubuntu 14.04 desktop. When the script runs, the CPU usage is only about 12%. The command I used was perl httpStresser.pl urlList rate 200.
Any idea why?
use AnyEvent;
use EV;
use AnyEvent::HTTP;
use AnyEvent::Handle;
use Time::HiRes qw( gettimeofday );
my $expectedRespCode = 200;
my $rate = 1;
my #urls = ();
readUrls(shift);
my $numOfUrls = $#urls;
my $start = time();
my $printed = 0; #have we printed the completion msg.
my $gId = 0;
my $spawned;
my #ctx = ();
my $i;
for ($i=0; $i<=$#ARGV; $i++) {
if ($ARGV[$i] =~ /^expect/) {
$expectedRespCode = $ARGV[$i+1];
$i++;
} elsif ($ARGV[$i] =~ /^rate/) {
$rate = $ARGV[$i+1];
print "rate is now $rate\n";
$i++;
} elsif ($ARGV[$i] =~ /^skip/) {
$gId = $ARGV[$i+1];
$i++;
} else {
die "only max, stayup are supported\n";
}
}
my $spawned = 0;
my $w = AnyEvent->condvar;
$| = 1;
my $start = getTS();
my $_timer;
$_timer = AnyEvent->timer(after => 0, interval => 0.001, cb => ::timeoutHandler);
$w->recv;
sub kickoff {
my $id = $gId ++;
if ($id > $numOfUrls) {
if ($printed == 0) {
print "done!!\n"; $printed = 1;
}
return;
}
#print "$id\n";
http_get $urls[$id], headers => { }, sub {
my $statusCode = $_[1]->{Status};
#printf "status $statusCode %d\n", time() - $start;
if (($id % 100) == 0) {
print "$id\n";
}
if ($statusCode != $expectedRespCode) {
print "unexpected resp code $id:$statusCode $urls[$id]\n";
}
};
}
sub timeoutHandler {
#print time(), "|\n";
if (! defined $start) {
$start = getTS(); kickoff(); $spawned = 1; return;
}
my $delta = getTS() - $start;
my $target = $delta * $rate;
#printf "%.4f %4d $spawned\n", $delta, $target;
for (; $spawned <= $target; $spawned++) {
kickoff();
}
if ($delta >= 1.0 ) {
$start += 1.0; $spawned = 0;
}
}
sub readUrls {
my $fname = shift;
my $line;
open FD, $fname || die "Failed to open $fname $!\n";
while (<FD>) {
chomp($line = $_);
push #urls, $line;
}
close FD;
}
sub getTS {
my ($seconds, $microseconds) = gettimeofday;
return $seconds + (0.0+ $microseconds)/1000000.0;
}

Optimize perl code

I have to write to check array ref for more than 3 params. If the value is coming from an array then I have written a foreach loop for that, then chop that and assign to a new variable after concatenating pipe.
Code
if ( defined $args->{hotel} ) {
if ( ref( $args->{hotel} ) eq "ARRAY" ) {
foreach my $hotel ( #{ $args->{hotel} } ) {
$hotel .= $hotel . "|";
}
chop($hotel);
$args->{hotel_name} = $hotel;
} else {
$args->{hotel_name} = $args->{hotel};
}
} else {
$args->{hotel_name} = $hotel;
}
if ( defined $args->{country} ) {
if ( ref( $args->{country} ) eq "ARRAY" ) {
foreach my $country_name ( #{ $args->{country} } ) {
$country_name .= $country_name . "|";
}
chop($country_name);
$args->{country_name} = $country_name;
} else {
$args->{country_name} = $args->{country};
}
} else {
$args->{country_name} = $country_name;
}
if ( defined $args->{city} ) {
if ( ref( $args->{city} ) eq "ARRAY" ) {
foreach my $city ( #{ $args->{city} } ) {
$city .= $city . "|";
}
chop($city);
$args->{city_name} = $city;
} else {
$args->{city_name} = $args->{city};
}
} else {
$args->{city_name} = $city;
}
I want to write a function for this kind of work so that there will be no repetition of same code. Please help me; how can we do this in Perl?
You can write:
sub convert_to_name ($$) { # ($value, $fallback_name)
my ($value, $fallback_name) = #_;
if (defined $value) {
if (ref($value) eq 'ARRAY') {
return join '|', #$value;
} else {
return "$value";
}
} else {
return $fallback_name;
}
}
$args->{'hotel_name'} = convert_to_name $args->{'hotel'}, $hotel;
$args->{'country_name'} = convert_to_name $args->{'country'}, $country;
$args->{'city_name'} = convert_to_name $args->{'city'}, $city;
There appear to be some potential bugs in your code, the biggest centering around reusing variable names at lower scopes.
However, of course you can add an iteration loop to your code that would remove the need for 3 nearly identical sections. The following does that by creating a intermediate hash data structure to relate field names to values.
Note: I also simplified the code by inverting the logic if your first if statement so that all ifs could be at the same level. Additionally, it makes sense to use a join instead of rolling your own such functionality.
my %hash = (
hotel => $hotel,
country => $country_name,
city => $city,
);
while ( my ( $field, $value ) = each %hash ) {
if ( !defined $args->{$field} ) {
$args->{"${field}_name"} = $value;
} elsif ( ref( $args->{$field} ) eq "ARRAY" ) {
$args->{"${field}_name"} = join '|', #{ $args->{$field} };
} else {
$args->{"${field}_name"} = $args->{$field};
}
}
Also, if you're comfortable with the Conditional operator, this can be reduced further. However, some would consider this too cluttered:
while ( my ( $field, $value ) = each %hash ) {
$args->{"${field}_name"} = !defined $args->{$field}
? $value
: ref( $args->{$field} ) eq "ARRAY"
? join( '|', #{ $args->{$field} } )
: $args->{$field};
}

Can't build perl class use autoload

class Gene
package Gene;
use strict;
use Carp;
use vars qw($AUTOLOAD);
my %_ok_gene_attr = (
"id" => "string",
"name" => "string",
"chrom" => "string", # chromosome or seq id
"txtStart" => "int", # 1-based
"txtEnd" => "int" # 1-based
);
sub new {
my ($class, %arg) = #_;
my $self = {};
$self->{_id} = $arg{id} || croak "no id";
$self->{_name} = $arg{name} || croak "no db";
$self->{_chrom} = $arg{chrom} || croak "no seq_id";
$self->{_strand} = $arg{strand} || -1;
$self->{_txStart} = $arg{txStart} || -1;
$self->{_txEnd} = $arg{txEnd} || -1;
$self->{_cdsStart} = $arg{cdsStart} || -1;
$self->{_cdsEnd} = $arg{cdsEnd} || -1;
$self->{_exonCount} = $arg{exonCount} || -1;
$self->{_exonStarts} = $arg{exonStarts} || -1;
$self->{_exonEnds} = $arg{exonEnds} || -1;
$self->{_score} = $arg{score} || -1;
$self->{_name2} = $arg{name2} || -1;
$self->{_cdsStartStat} = $arg{cdsStartStat} || -1;
$self->{_cdsEndStat} = $arg{cdsEndStat} || -1;
$self->{_exonFrames} = $arg{exonFrames} || -1;
bless($self, $class);
}
sub AUTOLOAD {
my ($self, $newvalue) = #_;
my ($operation, $attribute) = ($AUTOLOAD =~ /(get)(_\w+)$/);
unless ($operation && $attribute) {
croak "Method name $AUTOLOAD is not in the recognized form get_attribute\n";
}
unless (defined $_ok_gene_attr{$attribute}) {
croak "No such attribute '$attribute' exists in the class ", ref($self);
}
if ($operation eq 'get') {
*{$AUTOLOAD} = sub { shift->{$attribute} };
} elsif($operation eq 'set') {
*{$AUTOLOAD} = sub { shift->{$attribute} = shift };
$self->{$attribute} = $newvalue;
}
return $self->{$attribute};
}
sub DESTROY {
my($self) = #_;
$self->_decr_count( );
}
I use this class in
open my $in, '<', q/C:\Users\Jesse\Desktop\refGene.txt/ or die "Cannot open file : $!";
while(<$in>) {
chomp $_;
my #temp= split(/\t/, $_);
my $i=0;
my $temp1= Gene->new("id" => $temp[0],"name"=>$temp[1],"chrom"=>$temp[2], "strand"=>$temp[3],"txStart"=>$temp[4], "txEnd"=>$temp[5],"cdsStart"=>$temp[6],"cdsEnd"=>$temp[7],"exonCount"=>$temp[8],"exonStarts"=>$temp[9],"exonEnds"=>$temp[10],"score"=>$temp[11],"name2"=>$temp[12],"cdsStartStat"=>$temp[13],"cdsEndStat"=>$temp[14],"exonFrames"=>$temp[15]);
}
is has a error
(in cleanup) Method name Gene::_decr_count is not in the recognized form get_attribute
It also seems the autoload function doesn't work when i use $temp1->set_id("1234")?
Try this some kind of fixed version of Gene.pm:
package Gene;
use strict;
use Carp;
use vars qw($AUTOLOAD);
use Data::Dumper;
my %_ok_gene_attr = (
"_id" => "string",
"_name" => "string",
"_chrom" => "string", # chromosome or seq id
"_txtStart" => "int", # 1-based
"_txtEnd" => "int" # 1-based
);
sub new {
my ($class, %arg) = #_;
my $self = {};
$self->{_id} = $arg{id} || croak "no id";
$self->{_name} = $arg{name} || croak "no db";
$self->{_chrom} = $arg{chrom} || croak "no seq_id";
$self->{_strand} = $arg{strand} || -1;
$self->{_txStart} = $arg{txStart} || -1;
$self->{_txEnd} = $arg{txEnd} || -1;
$self->{_cdsStart} = $arg{cdsStart} || -1;
$self->{_cdsEnd} = $arg{cdsEnd} || -1;
$self->{_exonCount} = $arg{exonCount} || -1;
$self->{_exonStarts} = $arg{exonStarts} || -1;
$self->{_exonEnds} = $arg{exonEnds} || -1;
$self->{_score} = $arg{score} || -1;
$self->{_name2} = $arg{name2} || -1;
$self->{_cdsStartStat} = $arg{cdsStartStat} || -1;
$self->{_cdsEndStat} = $arg{cdsEndStat} || -1;
$self->{_exonFrames} = $arg{exonFrames} || -1;
bless($self, $class);
}
sub AUTOLOAD {
my ($self, $newvalue) = #_;
my ($operation, $attribute) = ($AUTOLOAD =~ /(get|set)(_\w+)$/);
unless ($operation && $attribute) {
croak "Method name $AUTOLOAD is not in the recognized form set_{attribute} or get_{attribute}\n";
}
unless (defined $_ok_gene_attr{$attribute}) {
croak "No such attribute '$attribute' exists in the class ", ref($self);
}
if ($operation eq 'get') {
no strict 'refs';
*{$AUTOLOAD} = sub { shift->{$attribute} };
} elsif($operation eq 'set') {
no strict 'refs';
*{$AUTOLOAD} = sub { shift->{$attribute} = shift };
$self->{$attribute} = $newvalue;
}
return $self->{$attribute};
}
sub DESTROY {
my($self) = #_;
$self->_decr_count( );
}
Tested with the following script:
#!/usr/bin/perl
use strict;
use warnings;
use Gene;
my $t1 = Gene->new(id=>"id", name=>"name", chrom=>"chrom");
$t1->set_id("1234");
print $t1->get_id(), "\n";
Beside using Perl's builtin OO system, you may also want to take a look of some other OO system, such as Moose. See perlootut for further details.

How can I catch a `shift`-`key` combination with Win32::Console::Input?

How can I catch a shift-some-key combination with this script?
When I press the Arrow-keys I get what I expect, but when I press shift-tab it doesn't return the KEY_BTAB value.
use warnings;
use 5.12.0;
use Win32::Console qw(STD_INPUT_HANDLE ENABLE_MOUSE_INPUT);
use constant {
RIGHT_ALT_PRESSED => 0x0001,
LEFT_ALT_PRESSED => 0x0002,
RIGHT_CTRL_PRESSED => 0x0004,
LEFT_CTRL_PRESSED => 0x0008,
SHIFT_PRESSED => 0x0010,
VK_LEFT => 0x25,
VK_UP => 0x26,
VK_RIGHT => 0x27,
VK_DOWN => 0x28,
VK_TAB => 0x09,
};
use constant SHIFTED_MASK =>
RIGHT_ALT_PRESSED |
LEFT_ALT_PRESSED |
RIGHT_CTRL_PRESSED |
LEFT_CTRL_PRESSED |
SHIFT_PRESSED;
my %d = (
KEY_DOWN => 258,
KEY_UP => 259,
KEY_LEFT => 260,
KEY_RIGHT => 261,
KEY_BTAB => 353,
);
my $con_in = Win32::Console->new(STD_INPUT_HANDLE);
$con_in->Mode(ENABLE_MOUSE_INPUT);
while ( 1 ) {
my $key = getch();
say "<$key>";
last if $key == 113;
}
sub getch {
my #event = $con_in->Input();
my $event_type = shift( #event );
if ( defined $event_type and $event_type == 1 ) {
my ( $key_down, $repeat_c, $vkcode, $vsccode, $char, $ctrl_ks ) = #event;
if ( $char ) {
return $char;
}
else {
if ( $vkcode == VK_UP and ( $ctrl_ks & SHIFTED_MASK ) == 0 ) {
return $d{KEY_UP};
}
elsif ( $vkcode == VK_DOWN and ( $ctrl_ks & SHIFTED_MASK ) == 0 ) {
return $d{KEY_DOWN};
}
elsif ( $vkcode == VK_RIGHT and ( $ctrl_ks & SHIFTED_MASK ) == 0 ) {
return $d{KEY_RIGHT};
}
elsif ( $vkcode == VK_LEFT and ( $ctrl_ks & SHIFTED_MASK ) == 0 ) {
return $d{KEY_LEFT};
}
elsif ( $vkcode == VK_TAB and $ctrl_ks == SHIFT_PRESSED ) {
return $d{KEY_BTAB}; # <--
}
else {
say "beep";
}
}
}
}
Output when I press shift and tab:
beep
<1>
<9>
<9>
beep
<1>
After editing the getch routine this way
sub getch {
my #event = $con_in->Input();
my $event_type = shift( #event );
if ( defined $event_type and $event_type == 1 ) {
my ( $key_down, $repeat_count, $virtual_keycode, $virtual_scancode, $char, $ctrl_key_state ) = #event;
if ( $char ) {
if ( $key_down ) {
return $char for $repeat_count;
}
}
else {
if ( $virtual_keycode == VK_UP and ( $ctrl_key_state & SHIFTED_MASK ) == 0 ) {
if ( $key_down ) {
return $d{KEY_UP} for $repeat_count;
}
}
elsif ( $virtual_keycode == VK_DOWN and ( $ctrl_key_state & SHIFTED_MASK ) == 0 ) {
if ( $key_down ) {
return $d{KEY_DOWN} for $repeat_count;
}
}
elsif ( $virtual_keycode == VK_RIGHT and ( $ctrl_key_state & SHIFTED_MASK ) == 0 ) {
if ( $key_down ) {
return $d{KEY_RIGHT} for $repeat_count;
}
}
elsif ( $virtual_keycode == VK_LEFT and ( $ctrl_key_state & SHIFTED_MASK ) == 0 ) {
if ( $key_down ) {
return $d{KEY_LEFT} for $repeat_count;
}
}
elsif ( $virtual_keycode == VK_TAB and ( $ctrl_key_state & SHIFTED_MASK ) == SHIFT_PRESSED ) {
if ( $key_down ) {
return $d{KEY_BTAB} for $repeat_count;
}
}
else {
say "beep";
}
}
}
}
I get this output:
beep
<1>
<9>
<0>
beep
<1>
When I run your code, $ctrl_ks has the value 48 (0x0030) when I hit Shift+Tab, and 32 (0x0020) when Shift is released. I think the check you want to make is
elsif ($vkcode==VK_TAB and ($ctrl_ks & SHIFT_PRESSED)==SHIFT_PRESSED) {
return $d{KEY_BTAB};
Firstly, $char is set to 9, so you never get to your check. Move the if ($char) check to somewhere more appropriate.
Secondly, your check is wrong. The following won't work if, say, Caps Lock is on.
elsif ( $vkcode == VK_TAB and $ctrl_ks == SHIFT_PRESSED )
You should only check the flags you are interested in.
elsif ($vkcode==VK_TAB and ( $ctrl_ks & SHIFTED_MASK ) == SHIFT_PRESSED)
Finally, sometimes you only get notified once for multiple presses. That is signaled by $repeat_count. You ignore this, so you potentially ignore keys.
You try to handle $repeat_count in the second snippet, but fail miserably. Part of the problem is you copied for $repeat_count from my other answer when it should be for 1..$repeat_count, and the other problem is that you only return one value even if $repeat_count is larger than one.
my #kbd_queue;
sub getch {
my #event;
if (#kbd_queue) {
#event = ( 1, #{ pop #kbd_queue } );
} else {
#event = $con_in->Input();
}
my $event_type = shift( #event );
if ( defined $event_type and $event_type == 1 ) {
my ( $key_down, $repeat_count, $virtual_keycode, $virtual_scancode, $char, $ctrl_key_state ) = #event;
return -1 if !$key_down;
if ( $virtual_keycode == VK_UP and ( $ctrl_key_state & SHIFTED_MASK ) == 0 ) {
push #kbd_queue, \#event for 2..$repeat_count;
return $d{KEY_UP};
}
...
elsif ( $virtual_keycode == VK_TAB and ( $ctrl_key_state & SHIFTED_MASK ) == SHIFT_PRESSED ) {
push #kbd_queue, \#event for 2..$repeat_count;
return $d{KEY_BTAB};
}
elsif ( $char ) {
push #kbd_queue, \#event for 2..$repeat_count;
return $char;
}
else {
say "beep";
}
}
}
You should convert this into something table-driven.