Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I'm writing a GUI that will take user input from the keyboard. When the user presses a key it calls a function, and when the key is released it calls another. This seems easy enough to do with Tk's KeyPress and KeyRelease. The problem I'm encountering, and it's probably the stupidest thing ever, is what happens when the key is held down. If the user holds the key down, I want the GUI to wait for the key release before doing anything. Instead, the keyboard/computer automatically starts pressing and releasing the key really fast and stops this when the user actually releases the key. How to I get around this? What I want to do is sort of like a video game. Press and hold left to move left, release to stop. The way it is now, I'll be starting and stopping really fast. Some sample code:
$top->bind('<KeyPress-KP_Left>' => \&left); $top->bind('<KeyRelease-KP_Left>' => \&stop);

Replies are listed 'Best First'.
Re: Tk bind KeyPress KeyRelease Problem
by PodMaster (Abbot) on Sep 12, 2004 at 02:52 UTC
    Instead, the keyboard/computer automatically starts pressing and releasing the key really fast and stops this when the user actually releases the key.
    Thats weird. When I run perlTk Type Tutor - useful for learning a DVORAK keyboard layout that only happens once (key press, key release) and after that a keypress is repeatedly fired until I release the key. This is only because I'm on my laptop with a ps2usb converter and a regular ps2 keyboard, and I have this problem with everything (really throws off my running in Quake3).

    When I use my laptops keyboard i don't see this at all.

    What kind of hardware setup do you have?

    MJD says "you can't just make shit up and expect the computer to know what you mean, retardo!"
    I run a Win32 PPM repository for perl 5.6.x and 5.8.x -- I take requests (README).
    ** The third rule of perl club is a statement of fact: pod is sexy.

Re: Tk bind KeyPress KeyRelease Problem
by zentara (Cardinal) on Sep 12, 2004 at 14:33 UTC
    I think you are running into the "automatic keyboard repeat" of the X windows system. (Probably the same idea on MSWindows). There are a couple of places to set these rates in Linux. One is the Option AutoRepeat in Xf86config, and the other is in /etc/X11/xkb/compat/norepeat ( on Slackware) where you can specify keys you don't want to repeat automatically.

    Both of these will make it harder to let others use your script so, you may be better off try to figure out a way of circumventing the repeat in your program.

    Off the top of my head, I thought this might work, but it fails too, but I think the idea is the right way to go. Somehow set a flag that the key is down, and then NOT start the function if it is set. Or maybe work out some flag which gets set when the function is first called, and prevents it from being started twice?

    This dosn't work, but crudely tries to demonstrate the idea. Maybe someone smarter knows the trick?

    #!/usr/bin/perl use warnings; use strict; use Tk; my $down = 0; my $mw = MainWindow->new; $mw->bind("<Key>", sub { &pressed } ); $mw->bind("<KeyRelease>", sub { &released } ); MainLoop; sub pressed{ if ($down == 1){return} else{print 'Key: ', ${Ev('K')}, " pressed\n"} $down = 1; } sub released{ print 'Key: ', ${Ev('K')}, " released\n"; $down = 0; }

    I'm not really a human, but I play one on earth. flash japh
      This dosn't work, but crudely tries to demonstrate the idea. Maybe someone smarter knows the trick?

      It's a great idea, and the code works perfectly for me on WinXP. I don't feel like rebooting to linux, so I won't try it now. What do you mean by "doesn't (sic) work"?

        "What do you mean by "doesn't (sic) work"?

        Well I think the OP wanted to turn a key into a switch which is on as long as it is held down, and is off when it is released. The code I posted will still "repeat" on linux, ( I hav'nt tried it on Windows). That is if you hold the key down, you will get a succession of "pressed-released events". If the OP had a binding to 'release' , then he would be repeatedly starting-stopping.

        Just for fun, and because I think this could be useful in my "bag-o-tricks", I tried a few more things, but was still unsuccessful. For example:

        #!/usr/bin/perl use warnings; use strict; use Tk; my $mw = MainWindow->new; $mw->bind("<Key>", sub { &pressed } ); $mw->bind("<KeyRelease>", sub { &released } ); MainLoop; sub pressed{ print 'Key: ', ${Ev('K')}, " pressed\n"; $mw->bind("<Key>", sub { } ); $mw->bind("<KeyRelease>", sub { } ); } sub released{ print 'Key: ', ${Ev('K')}, " released\n"; }
        This code above will break the "auto-repeat" of the X-windows, but I can't seem to find a way to "re-enable the key bindings" when the key is actually released. I tried adding timers, which watch the output, expecting a succession of "pressed" events, and if the output stops, then call the "stop_function".

        For example:

        #!/usr/bin/perl use warnings; use strict; use Tk; my $mw = MainWindow->new; $mw->bind("<Key>", sub { &pressed } ); MainLoop; sub pressed{ print 'Key: ', ${Ev('K')}, " pressed\n"; $mw->bind("<Key>", sub { } ); $mw->bind("<KeyRelease>", sub {&pressed } ); }
        This will just print a succession of "pressed" as long as the key is held down, and will stop when the key is released. So I thought about opening a FH to \$tempvar, and writing the output to it, then have a timer, truncate FH after a time period. If the length($tempvar) == 0, then I know the key was released, and can call the stop_function. But the timers eluded me, because either I spawned multiple timers, or the timing of the timers was funky, and unusable. But as of now I think this is the only method that will work, but it depends on what the "internal repeat key rate" is on your system. So after all that, I figure, it would be better to take a different approach, like have 1 key to initiate, and a differently named key to stop. (Which is normally how it's done).

        I'm not really a human, but I play one on earth. flash japh
Re: Tk bind KeyPress KeyRelease Problem
by Anonymous Monk on Sep 13, 2004 at 23:53 UTC
    I figured out a sort of work around. As far as I can tell, the Control, Shift, and Alt keys all work they way I'd like my other keys to work (only one KeyPress/KeyRelease). I used that to do this:
    $top->bind('<Control-KP_Left>' => sub{ &pressed("left",1,2) }); $top->bind('<Control-KP_Down>' => sub{ &pressed("back",0, 7) }); $top->bind('<Control-KP_Right>' => sub{ &pressed("right",5, 6) }); $top->bind('<Control-KP_Home>' => sub{ &pressed("spinl",1, 3, 5, 7) } +); $top->bind('<Control-KP_Up>' => sub{ &pressed("forward",3, 4) }); $top->bind('<Control-KP_Prior>' => sub{ &pressed("spinr",0, 2, 4, 6) } +); sub pressed { my @args = @_; my $arg = shift @args; &fire_thrusters(@args); for ($arg){ /left/i && do { $top->bind('<Control-KP_Left>' => sub{}); }; /right/i && do { $top->bind('<Control-KP_Right>' => sub{}); }; /back/i && do { $top->bind('<Control-KP_Down>' => sub{}); }; /forward/i && do { $top->bind('<Control-KP_Up>' => sub{}); }; /spinl/i && do { $top->bind('<Control-KP_Home>' => sub{}); }; /spinr/i && do { $top->bind('<Control-KP_Prior>' => sub{}); }; } $top->bind('<KeyRelease-Control_R>' => sub{ &released($arg) }); $top->bind('<KeyRelease-Control_L>' => sub{ &released($arg) }); } sub released { my $arg = shift; &stop; for ($arg){ /left/i && do { $top->bind('<Control-KP_Left>' => sub{ &presse +d("left",1,2) }); }; /right/i && do { $top->bind('<Control-KP_Right>' => sub{ &presse +d("right",5, 6) }); }; /back/i && do { $top->bind('<Control-KP_Down>' => sub{ &presse +d("back",0, 7) }); }; /forward/i && do { $top->bind('<Control-KP_Up>' => sub{ &presse +d("forward",3, 4) }); }; /spinl/i && do { $top->bind('<Control-KP_Home>' => sub{ &presse +d("spinl",1, 3, 5, 7) }); }; /spinr/i && do { $top->bind('<Control-KP_Prior>' => sub{ &presse +d("spinr",0, 2, 4, 6) }); }; } $top->bind('<KeyRelease-Control_R>' => sub{}); $top->bind('<KeyRelease-Control_L>' => sub{}); }
    Now the user can press Ctrl-8 and hold Ctrl until they want to call the &released function. It's not nearly as cool as just holding down the Numpad keys, but I guess it'll work. If someone has a better solution please post.
Re: Tk bind KeyPress KeyRelease Problem
by zentara (Cardinal) on Sep 14, 2004 at 16:41 UTC
    Hi again, I think I have something that works. I couldn't give up on this, because something in my subconcious mind told me it had to be possible. It was just a question of "pulling it out" with all it's multiple feedback complexities. So the code below does it with a counter and 2 timers. The first timer will set and reset itself as the key auto repeats, and it will reset the counter to 0 after 300 ms. If the key is held down, the counter should never hit 0. The second timer, waits for the counter to hit 0, and will call the stop function, and reset the bindings. I have it setup with 300 and 400 milliseconds, and that seems to work on my machine. If you go too low, like to 50 and 100 ms, you will get a single repeat sneaking thru, before the counter can setup. So this timer has to be set higher than the repeat rate on your machine. I'm sure this code can be refined, but it's good enough for a "proof of concept".
    #!/usr/bin/perl use warnings; use strict; use Tk; my $counter = 0; my $timer; my $timer1; my $timerrunning = 0; my $mw = MainWindow->new; $mw->geometry('+250+250'); $mw->bind("<Key>", sub { &pressed } ); $mw->bind("<KeyRelease>", sub { } ); MainLoop; #----------------------------------- sub pressed{ my($widget) = @_; my $e = $widget->XEvent; # get reference to X11 event structure if($timerrunning == 1){ $timer->cancel; &start_timer} else{&start_timer} $counter++; print "$counter\n"; &do_function( $e->K.'-pressed' ); } #----------------------------------- sub start_timer{ $timerrunning = 1; $counter++; $timer = $mw->after(300, sub{$counter = 0; $timerrunning = 0; &start_timer1; $timer->cancel; }); } #------------------------------------- sub start_timer1{ $timer1 = $mw->after(400, sub { if($counter == 0){&stop_function} }); } #-------------------------------------- sub do_function{ my $key = shift; print "$key\n"; } #---------------------------------------- sub stop_function{ print "Stopping\n"; $mw->bind("<Key>", sub { &pressed } ); $timerrunning = 0; $counter = 0; } #----------------------------------------

    I'm not really a human, but I play one on earth. flash japh