KeyRepeat - forever problem (please confirm)

Archives Forums/Blitz3D Bug Reports/KeyRepeat - forever problem (please confirm)

Danny(Posted 2013) [#1]
Many clients complained that 'Keys get stuck' after holding down the cursor keys for several seconds (to navigate a camera). On some computers/systems this happens more than others.

Some sort of infite-key-repeat seems to kick it: FALSELY reporting 'KeyDown(x)=True' events, even if the Key has been up for minutes or hours!

The worst of all is that when this 'key-repeat' bug occurs: FLUSHKEYS DOES NOT WORK! Which is really robbing me of a fix!! :((

The only way to stop it, is to HIT the exact SAME key again - Assuming you know 'which key' is was that got stuck!

Please test the below code - and follow the instructions to reproduce the problem.

Normally people might not be 'holding a key and clicking away' - but this is just a vehicle to reproduce the problem. This bug occurs especially when using ALT-TAB, Ctrl-Alt-Del > task manager, or Win-L, etc. and other conditions when you 'move focus away from b3d BEFORE the keyboard buffer is fully consumed'...!?

It's been reported with B3d 1.98 - 1.106.
It's been reported on Vista, Win7 and Win8.

WILL PAY FOR A FIX!

Regards,

Danny.


;; Infinite key-repeat show-stopper :

Print "1. Hold down the space bar for a few seconds."
Print "   (observe correct 'key down state' in debuglog)"
Print ""
Print "2. Whilst holding Space: Click outside the
Print "   runtime window. THEN release the space bar."
Print ""
Print "3. Now observe the debuglog Falsely Reporting (!)"
Print "   the space bar is still down -when it isn't!"
Print "   :("
Print ""
Print ""
Print "  HIT ESC TO EXIT.."

;no effect:
;EnableDirectInput False

While Not done
	
	; 57=space bar
	If KeyHit(57)
		DebugLog "SPACE State: HIT"
	ElseIf KeyDown(57)
		DebugLog "SPACE State: Down....... @ "+MilliSecs()
	Else
;		DebugLog "SPACE State: up          @ "+MilliSecs()
	EndIf
	
	; ESC to exit
	If KeyHit(1) Then End
Wend
End



Floyd(Posted 2013) [#2]
When your program doesn't have the focus the keyboard input is going "elsewhere".

SetBuffer BackBuffer()    
ms = MilliSecs()   
      
Repeat
	Cls
	k = k + KeyHit( 57 )
	Text 190, 100, k
	Text 190, 160, ( MilliSecs() - ms ) / 1000 
	Flip
Forever

Create an executable and run it twice, so there are two windows on screen. They are in the same place so you have to drag them around to see both.

Click in either window and it will respond to the space bar. Click somewhere on the desktop and neither window will will get any keyboard input.

I know it is possible to set up "hot keys" in a Windows program, so it gets a message even without the focus. But I don't know the details and can't tell you if it is feasible with Blitz3D.

Maybe you could just tell people to click back in the program window, even if minimized.


Danny(Posted 2013) [#3]
Thanks Floyd,

But sorry I think you don't understand. Yes, keyboard input 'is supposed' to go "elsewhere" - I agree. But bug describes a situation where it Does not !! - hence my problem..

Hits work fine, but KEYDOWN does NOT! Replace the line with: "k=k+KeyDOWN(57)" then:

1. run your program.
2. HOLD the space (see counter increase)
3. Whilst holding space, click away from the app window: see the counter STILL increasing!!
I'm getting keys where I shouldn't!!
4. This key-repeat will loop INFINITELY! Until you HIT the same key again!

Please realize: This bug also occurs when not doing a 'hold key + click away' but (CAN) happen at many moments when the app loses focus AND there are still keys in the buffer! Like pop ups from other programs (skype, mail) and so on - whilst e.g. you're holding a cursor key to drive a camera! On Win7 and especially Win8 (slow as Vista) it's getting more frequent!

It's like Russian roulette: When it happens, the user is stuck (can't use keyboard) and has to save data (if possible) and relaunch the app!

Danny.


Floyd(Posted 2013) [#4]
Right, I didn't understand the problem.

But it still seems to be the same fundamental issue. The program has been told that the space bar was pressed. It continues to believe the key is pressed until it receives a "key released" event. When focus is regained the next key up for the spacebar will send such an event to the program.

I would offer a solution if I knew one.


Danny(Posted 2013) [#5]
That's it indeed. I'm trying to hack into the keyboard message queue to clear/flush any (keyboard) events. But for some reason (which normally works fine) also has no effect in this case.

# I tried 'faking key up & down events' to 'un-stick it' using api_keybd_event(), but no luck.

# I've tried dumping an entire new keyboard buffer (with all keys in up-state) using api_SetKeyboardState(); didn't work.

# I've tried purging the (keyboard) message queue using api_PeekMessage & PM_REMOVE, and wb3d_flushevents(); guess what: no luck either.

# I've stabbed around with 'EnableDirectInput False' and 'wb3d_force_blitzKeys(false)'

# I've searched WS_ style flags on the runtime window, to see if I can affect the message queue to a positive effect..

# I've tried anything short of cyanide, C4 and mooning at it, basically.

This story might have started 10 years ago:
http://www.blitzbasic.com/Community/posts.php?topic=21418


running out of steam...


Kryzon(Posted 2013) [#6]
Hi Danny, I tested something and it seems to work.
Basically, there's a Win32 API function that returns the HWND of the currently active window. In other words, it returns which window the user has "selected".

When the user does a CTRL+ALT+DEL, ALT+TAB etc., he's switching the focus to either the desktop, the task manager or any application other than your own.

What you need to do is simply 'not check' for keyboard input in case the handle of the current focused window is different than the handle of your blitz app's window.

You need GetFocus() from User32. Save it as "getfocus.decls" in your userlibs folder.
.lib "user32.dll"

api_GetFocus% () : "GetFocus"

And how you use it:
Graphics 320,240,0,2

Local myAppHWND = SystemProperty("AppHWND") ;Retrieve the HWND of my blitz app.
Local counter=0

While Not done
	Cls
	Locate 0,0
	Print "My HWND: "+myAppHWND
	Print "The focused HWND: "+api_GetFocus()
	Print counter 	
	
	If ( api_GetFocus() = myAppHWND ) Then ;If my app has focus then I can listen to keyboard and mouse inputs.
		; 57=space bar
		If KeyDown(57) Then counter = counter + 1
	EndIf 
	
	; ESC to exit
	If KeyHit(1) Then End
	Flip
Wend
End



Danny(Posted 2013) [#7]
Appreciate the input Kryzon and I can follow what you're doing.
It's a workaround to prevent re-acting to (false) input when the app doesn't have focus. Which is fine.

But it doesn't make the problem go away!
As soon as you return to your window, the App will 'STILL think there are keys down' when they clearly aren't. And e.g. my character suddenly starts 'wandering off on it's own accord!' - I can't sell that ;)

I AM able to detect which exact keys are stuck - but I can't seem to be able to 'flush the keyboard buffer' from the top-down.


Danny(Posted 2013) [#8]
I've tried anything I could come up with -to somehow purge/kick/clear the keyboard buffer / win32 message queue and stop giving the False KeyDown status. But safe to say: nothing worked that normally would (certainly not FlushKeys!).

So if I can't affect this at Blitz3D's own 'hWnd level' - does anyone know where I might poke and stab 'higher up' then?! I have no clue to be honest, I'm just hacking in all directions trying to make it go away ;)

Cheers,

Danny.


Kryzon(Posted 2013) [#9]
Okay, after a bit of testing. Using only focus-checking like the above is half the solution: it stops considering key input when the program is out of focus.

However, like you said, when you return to the program the key is still considered as "pressed". This is the other half of the problem left to solve.
To address this issue, you need to code that whenever you return focus to your program, you emit false "KEY UP" events to all the keys you are listening for in your program. A "KEY UP" event means, "this key was released. It's not being pressed anymore."
If your program listens for space, left-control, enter, shift etc., you need to emit these false events for all these keys once you return focus to your program - and you just have to do this once, to eliminate that perpetuous "pressed" state (you don't necessarily have to emit events for all keys, just the ones that were pressed when the focus was lost - but it's better to be safe and do it for all).

To do this, you need yet another function from User32: keybd_event. It's actually an old function (SendInput is the modern, preferable version, but it's much more complicated than what I have time for), but seems to work so far.

1) You need this decls in the end:
.lib "user32.dll"

api_GetFocus% () : "GetFocus"
api_keybd_event (bVk%, bScan%, dwFlags%, dwExtraInfo%) : "keybd_event"
2) Then you need to list all the virtual-key-codes as well as the hardware break-scan-codes for the keys you are using in your program. Each key has a specific pair of these values.
You can find them here: http://www.codeproject.com/Articles/7305/Keyboard-Events-Simulation-using-keybd_event-funct

3) Then proceed to use these two API functions in the following way:
Graphics( 320, 240, 0, 2 )

Local myAppHWND = SystemProperty( "AppHWND" ) ;Retrieve the HWND of my blitz app.
Local counter=0
Local lost=False

While Not done
	Cls
	Locate 0,0
	Print "My HWND: "+myAppHWND
	Print "The focused HWND: "+api_GetFocus()
	Print counter 	
	
	If ( api_GetFocus() = myAppHWND ) Then ;If my app has focus then I can listen to keyboard and mouse inputs.
		If lost Then
			;If the program was previously out-of-focus, clear the keys I'm using to prevent unwanted behavior.
			ActuallyFlushKeys()
		EndIf 
	
		;The SPACE key is being tested here.
		If KeyDown( 57 ) Then counter = counter + 1
		
		lost = False
	Else
		lost = True
	EndIf 
	
	If KeyHit( 1 ) Then End
	Flip
Wend


Function ActuallyFlushKeys()
	Local KEYEVENTF_KEYUP = 2
	
	api_keybd_event( $20, $B9, KEYEVENTF_KEYUP, 0 ) ;SPACE key.
	api_keybd_event( $A2, $9D, KEYEVENTF_KEYUP, 0 ) ;LCONTROL key.
	api_keybd_event( ... )
	[...]
End Function

End

This effectively eliminates the problem with perpetuous key presses when you leave and return to your program.


GaryV(Posted 2013) [#10]
This story might have started 10 years ago:


I do not think that applies. DirectInput is not supposed to be used anymore. I remember reading an old post where Mark switched to WM_INPUT for BlitzPlus. I think he made the switch for Blitz3D, too.


Danny(Posted 2013) [#11]
** THANK * YOU * KRYZON ** For ending a 12+ Month nightmare!

I tried the keybd_event() approach: but I did not know about the 'make or break' scancode variations!
I was feeding the original b3d scancodes - instead of adding the 0x80!

Also, your method of fixing it 'at the time of losing focus' is much more sensible that my attempts to fix it 'when coming back in focus'.

I'll contact you privately, as promised.

Thanks again, as well as the other guys that chipped in!

Danny


Danny(Posted 2013) [#12]
MAN! I can't believe this- just when I thought I was done :)

OK, so it works for all keys with a B3D SCANCODE < 128!
Because the 'break code' seems to be identical to the B3D ScanCode + $80.
Which means scancodes > 128 have a problem +$80 will be > 255 = not good.

I've searched half the day, but does anyone know what the break codes for CURSOR keys for example?! (Scancodes > 200)

The only 2 source files I could find mentioned that the break code for e.g. cursor keys is the same as the scan code (e.g. $25 for LEFT).
see e.g: http://cboard.cprogramming.com/game-programming/132698-help-snake-game.html

But that doesn't seem to work, test here using LEFT key instead.
It appears to stop - but upon re-focus the counter goes on :(((




Kryzon(Posted 2013) [#13]
Hi Danny. I've done some more tests, tried wrapping SendInput etc. etc. but no success either.
The problem is really trying to work out how Blitz3D handles input internally, and there's no information about that.

With this in mind, there is a way to "fix" it, which is just to not use Blitz's KeyDown function.
Instead, use the Win32 API: GetAsyncKeyState.
It works just like KeyDown, but without this focus lock-up issue.

Keep in mind this API function can return a pressed key state even when your app doesn't have focus (unlike the original KeyDown, which only gives positive results when your app has focus).
So it's important to just receive your AsyncKeyStates if you know your app has focus - otherwise people may want to browse their e-mail etc. while the game is paused, and accidentaly unpause it.

This is all you need:
.lib "user32.dll"

api_GetFocus% () : "GetFocus"
api_GetAsyncKeyState% ( vKey% ) : "GetAsyncKeyState"
Graphics( 320, 240, 0, 2 )
SetBuffer BackBuffer()

;Virtual key-code.
Const VK_LEFT = $25 ;Left Arrow.

;Blitz scan-code.
Const SC_ESCAPE = 1 ;Esc.

Local myAppHWND = SystemProperty( "AppHWND" )
Local counter = 0
Local done = False

While Not done
	Cls
	
	;Only receive KeyDown input if my program has focus.
	If ( api_GetFocus() = myAppHWND ) Then
		
		If api_GetAsyncKeyState( VK_LEFT ) Then counter = counter + 1
		
	EndIf 

	;Regular blitz KeyHit, no need to worry about app focus.
	;But for the sake of organization you can put all your input tests inside that GetFocus block above.
	If KeyHit( SC_ESCAPE ) Then done = True

	Text( 10, 10, "App HWND: "+myAppHWND )
	Text( 10, 30, "Current: "+api_GetFocus() )
	Text( 10, 50, counter )

	Flip
Wend

End
So for KeyHits you can still use Blitz3D's, but you can replace every KeyDown with GetAsyncKeyState as demonstrated.
You can also use a name other than api_GeAsyncKeyState in the .decls, maybe something more friendly like KeyDown2 or AsyncKeyDown.