SDL: No unicode information in key releases

This topic is 4919 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

Hello everyone I've got a problem; once there was discussion of possible SDL_GetKeyState drawbacks and why it's better to use events on SDL mailing list. I've read many mails that adviced me to change to event-driven model, and because of that, I rewritten my input manager to use events; it worked perfectly (well, at least it helped me to overcome "bug" that didn't let you type uppercase characters :-)) until today, when I discovered that my edit control in GUI doesn't respond to keypresses. I fixed it, but then, I've seen that actuall test demo (that operates with keyboard just like real game will do) doesn't respond to keypresses. I fixed it, but then again, GUI wasn't working as expected - when pressing anything combined with shift, that key reported keypresses even when in reality, it was released long ago. I've investigated code and it all comes to one line of code, let me show you:

// it's called at the beginning of main loop:

bool SC :: InputTask :: Start()
{
// ignore to speed things up in the Kernel and InputTask Update sections that loop through all received events.

SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
SDL_EnableUNICODE(1);
if ( SDL_EnableKeyRepeat(300, 30) == -1)
logError2("Input", "Couldn't set key repeat value.")

// SDL_GetKeyState returns a reference to the key state array, not a copy, so we must copy it manually
tmpKeys = SDL_GetKeyState( &keyCount );

actKeys = new uchar[keyCount];
if (!actKeys) return false;

oldKeys = new uchar[keyCount];
if (!oldKeys)
{
Stop();
return false;
}

// twice to flush any pending keypresses
SDL_PumpEvents();
SDL_PumpEvents();

memset(oldKeys, 0, keyCount);
memset(actKeys, 0, keyCount);
return true;
}

--------------------------

// it's called every frame

void SC :: InputTask :: Update()
{
SDL_PumpEvents();

// this construction dispatches up to 8 keypresses each frame
maxEventCount = 8;
// copy actual keypresess to old ones
memcpy( oldKeys, actKeys, keyCount);

//  memset(actKeys, 0, keyCount);   // ###    it's here !!!!

while ( (SDL_PollEvent ( &event )) && (maxEventCount))
{

if (event.type == SDL_KEYDOWN)
{
if ((event.key.keysym.unicode ) == 0)
actKeys[event.key.keysym.sym] = 1;
else
actKeys[event.key.keysym.unicode] = 1;
}
else if (event.type == SDL_KEYUP)
{
actKeys[event.key.keysym.sym] = 0;  // ***
}
--maxEventCount;
}

}


The problem really lies in that the key realeses doesn't have unicode codes attatched, so if we press ie. shift + 1 (which on my keyboard produces !) then it's keysym.sym and keysym.unicode will vary, and therefore -> in this line (***) it won't toggle old unicode off, which will forever produce keypresses for !. I've found possible solution to this: each frame, clear actual keys so that even when sth was pressed, it won't stay forever (###). It makes GUI work, but test game won't respond to any keypressess at all. If I comment that line (just like it's now) then game will work, but GUI will suffer. Though, there's hope - when I allow for dynamically choosing of in what mode should input manager work (bool flag: clear each frame actKeys or not clear), then, if I remember to switch modes, GUI and game should work correctly. However, I don't like to remember about such things - it's counter intuitive and introduces stupid "bugs", it would be good if it was handled by input manager internally (in some way or another). Ehhmm, so, now the question ;-) What are other alternatives to my solution? It would be best if there was some way to dig to that unicode code even for releases... but it's probably impossible. Thanks in advance!

Share on other sites
Ehhmmmm... does this silence mean that nobody has encountered similiar problem? :-/ SDL is quite often used as library for handling user input... events are also used quite often... so, if your input system uses SDL with event based model and it's working fine - please post your code here.

Share on other sites
Quote:
 Original post by KoshmaarEhhmmmm... does this silence mean that nobody has encountered similiar problem? :-/ SDL is quite often used as library for handling user input... events are also used quite often... so, if your input system uses SDL with event based model and it's working fine - please post your code here.

Well I do not know why you have choosen a unicode based system - which is why I have not yet replied, but this is my input code that worked well for me.

class Immortal_Input{private:	Uint8* m_keys;	int m_lmbdown;	int m_lmbclick;	int m_rmbdown;		int m_rmbclick;	int m_curkey;	int m_mx;	int m_my;public:	Immortal_Input();	~Immortal_Input();	void Update();	inline void ClearKey(int key);	inline void ClearKey();	inline bool KeyDown(int key);	inline bool LMBClick() const;	inline bool RMBClick() const;		inline bool LMBDown() const;	inline bool RMBDown() const;		inline int MouseX() const;	inline int MouseY() const;};

CPP File:
Immortal_Input::Immortal_Input(){	m_curkey = 0;	m_lmbclick = 0;	m_rmbclick = 0;	m_mx = 0;	m_my = 0;	m_lmbdown = 0;	m_rmbdown = 0;	m_keys = 0;}Immortal_Input::~Immortal_Input(){}inline bool Immortal_Input::LMBClick() const{	return (m_lmbclick == 2);}inline bool Immortal_Input::RMBClick() const{	return (m_rmbclick == 2);}inline bool Immortal_Input::LMBDown() const{	return (m_lmbdown == 1);}inline bool Immortal_Input::RMBDown() const{	return (m_rmbdown == 1);}inline int Immortal_Input::MouseX() const{	return m_mx;}inline int Immortal_Input::MouseY() const{	return m_my;}inline bool Immortal_Input::KeyDown(int key){	m_curkey = key;	return (m_keys[key] == 1);}inline void Immortal_Input::ClearKey(){	if( m_curkey > 0)	{		m_keys[m_curkey] = 0;		m_curkey = 0;	}}inline void Immortal_Input::ClearKey(int key){	m_keys[key] = 0;	m_curkey = 0;}void Immortal_Input::Update(){	if(m_lmbclick != 1)		m_lmbclick = 0;	if(m_rmbclick != 1)		m_rmbclick = 0;	m_keys = SDL_GetKeyState(NULL);	SDL_GetMouseState(&m_mx, &m_my);		if( SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_LEFT))	{		SDL_GetMouseState(&m_mx, &m_my);		m_lmbdown = 1;		m_lmbclick = 1;	}	else	{		if(m_lmbclick == 1)			m_lmbclick = 2;		m_lmbdown = 0;	}		if( SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT))	{		SDL_GetMouseState(&m_mx, &m_my);		m_rmbdown = 1;		m_rmbclick = 1;	}	else	{		if(m_rmbclick == 1)			m_rmbclick  = 2;		m_rmbdown = 0;	}}

It should be what you need. Basically you can test to see if a key is down with KeyDown - however, as long as the key is pressed, the events will still be generated. So if you want only one message per key press - no matter how long the key is held down, you will need to make a call to ClearKey.

Here is a quick little sample:
// Define the variableImmortal_Input inp;// Somewhere in the beginning of the update loopinp.Update();// Later in the updateif( inp.KeyDown(SDLK_RETURN) ){    // Code here    // inp.ClearKey(); // if you add this line only one event will be generated for RETURN key press per button press// As of now as long as the button is down, the events will be generated}

If you need any help with this let me know. I am off to class now, so I'm short on time, but this should give you a head start. Let me know and goodluck!

- Drew

Share on other sites
Well, I was using SDL_GetKeyState and it was working... untill one day, when I realized that pressing shift or caps lock won't give me uppercase characters. I've written about this problem to SDL mailing list, and after long and hot discussion, when Lantinga (that Sam Lantinga...) and Bob Pendelton adviced me strongly to switch to event based input, I did that. Quotes:

Bob Pendelton:
Quote:
 Since the key vector shows you the state at the time you check it, it ispretty easy to miss key presses and shift presses. People don't pressthem at the same time so you might get wrong results at times. The onlyway around that problem is to use a form of input that doesn't let youmiss key presses/releases.

Sam Lantinga:
Quote:
 As was mentioned elsewhere, if you rely on the current state of the keyboard,you'll miss keys that were pressed and then released before you polled thekeyboard state.Since most operating systems handle keyboard and mouse input in an input queue,and you only have a snapshot of the keyboard state at any given time, the onlyway that I know of to catch keyboard state changes as they occur is to processevents and handle them appropriately.Basically SDL_GetKeyState() gives you the current state after all eventshave been processed, so if a key or button has been pressed and releasedbefore you process events, then the pressed state will never show up inthe getstate calls.Well, yes, but that won't solve your problem. The SDL keysyms only correspondto unmodified keys on the keyboard. For example, the French keyboarddoesn't actually have number keys. :) To get '1' on a French keyboard, youhave to press shift and another key (I forget which), so you'll never get akeyboard event with SDLK_1 in it on a French keyboard. If you enable UNICODEtranslation though, you'll get an event with '1' in the unicode field when theappropriate key combination is pressed.

There were also other arguments, but they were slightly less important.

Quote:
 If you need any help with this let me know. I am off to class now, so I'm short on time, but this should give you a head start. Let me know and goodluck!

I know how to write input system (it was done long ago, but it used SDL_GetKeyState, which caused problems, so I had to switch to events etc.), the real problem lies within bloody details which are explained in my first post.

Share on other sites
Ok thank you for your time and patience in explaning this problem. I see what you are talking about now. I have worked out a little example that I think you may find what you need. I do not know - so let me know. I have not added any comments so if you can't follow anything - just ask. [smile]

#include <stdio.h>#include <stdlib.h>#include <SDL/SDL.h>#pragma comment (lib, "SDL.lib")#pragma comment (lib, "SDLmain.lib")#include <vector>using namespace std;vector <Uint8> keydown_list;vector <Uint8> donotrelease_list;int main( int argc, char *argv[] ){	keydown_list.clear();	donotrelease_list.clear();	keydown_list.resize(0xFFFF);	if ( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0 )	{		printf("Unable to init SDL: %s\n", SDL_GetError());		exit(1);	}	atexit(SDL_Quit);	SDL_Surface *screen;	SDL_EnableUNICODE(1);		screen=SDL_SetVideoMode(640,480,32,SDL_HWSURFACE|SDL_DOUBLEBUF);		if ( screen == NULL )	{		printf("Unable to set 640x480 video: %s\n", SDL_GetError());		exit(1);	}		int done=0;	while(done == 0)	{		SDL_Event event;		while ( SDL_PollEvent(&event) )		{			if ( event.type == SDL_QUIT )  			{  				done = 1;  			}			if ( event.type == SDL_KEYDOWN ) // keydown			{				keydown_list[ event.key.keysym.unicode ] = 1;				donotrelease_list.push_back(event.key.keysym.unicode);                                 // If you want to store any of this as well...				if( event.key.keysym.unicode )				{					//printf( "UNI: %i\n", event.key.keysym.unicode );					//printf( "SYM: %i\n", event.key.keysym.sym );					//printf( "MOD: %i\n\n", event.key.keysym.mod );				}			}			int ind = 0;			int current = 0;			if( donotrelease_list.size() > 0 )			{				current = donotrelease_list.at(ind);				for(int x=0;x<0xFFFF;x++)				{					while(x < current)					{						keydown_list[x] = 0;						x++;					}					ind++;					if(ind >= donotrelease_list.size())						current = 0xFFFF;				}				donotrelease_list.clear();			}			else			{				for(int x=0;x<0xFFFF;x++)				{					keydown_list[x] = 0;				}			}		}		for(int x=0;x<0xFFFF;x++)		{			if(keydown_list[x])			printf("%i - %i\n",x,rand()%100);		}		SDL_Flip(screen);	}	return 0;}

I tested it and it works. Hope this helps! I added in the rand()%100 to show that the key really is released and pressed for the time interval it is held. Also note that it loops through all the keys and outputs them - so as you hold down shift - it will generate shift messages, but as soon as you hit another key - the right one is sent. Also this probabally could be optimized a lot more [wink].

- Drew

[Edited by - Drew_Benton on June 16, 2005 5:02:27 PM]

Share on other sites
Thanks for taking your time trying to understand my foolish explanations ;-) also thanks for writing this example program, ++rating for you.

Actually, I've also written with this problem (again) on SDL mailing list and one guy (Xavier Joubert) answered me this way:

Quote:
 > In short: I know that I can't get unicode information from key releases. I> know ugly solution to make this work (switching between 2 modes etc. as> explained in first letter). I know that on SDL mailing list there are whole> lots of advanced programmers who are writing games using SDL and event based> input; it would be unbelievable if no one has ever encountered this problem> (at least) and solved it (that's why I'm writing here and what I'm interested> in). Are there any... hmmm, hacks which I could use?There's no hack. Nobody store keypresses in an array for use with a GUI like youdo.Handling keyboard for a GUI and game control are two different things.In a game context, whenever a key is pressed, you store in a array the fact thekey is down, so you will react accordingly in your input handling code as longas it is not released. For example, if left is down, you will move player somepixels to the left. When the key is released, you clear its state. That's whatyou're doing in your code.In a GUI context, whenever a key is pressed, you react to that event. If left ispressed, you move cursor one char to the left. That's it. You don't need tostore anything or even handle SDL_KEYUP events. Of course, you need to useSDL_EnableUNICODE() to get usuable infos. You will also useSDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL), toproperly handle key repeat.So, you need to switch from one mode to the other depending on the context. Forexample when you see in your main game loop that user pressed, say, F12, thenyou clear all key pressed, set a flag saying you're in GUI mode, and draw yourdialog box. Depending on the type of game (realtime or not), you will go toanother loop (in which case the games is paused) or continue in the main one,but handling keyboard differently since you're in GUI mode.

Currently I don't have time to change my input system (because those wouldn't be trivial changes) to make such things like your program do (I don't have time to analyse it fully too :-/ ) so I'll just stick to Xavier's advice (which is exactly that, what I thought is the only way to overcome this problem)... probably he's right.
Anyway, thanks again.

Share on other sites
Awesome! I guess that makes a lot of sense. If you have time you could take a look into using function pointers to speed up the input system. Instead of having something like:
if( GUI ){   ... GUI Code ...}else if{   ... Game Code ...}else{   ... special cases ...}

You could use a function pointer that you set when you switch between the two, ie.
void (*ptInputSystem)();...Init{    ptEndDraw = GUIInput;}...void HandleInput(){    (*ptInputSystem)();}...GUIInput(){...if (start game)    ptEndDraw = GameInput;}GameInput(){}

That's just some pseudo code as an idea. The concept of function pointers would be definitly faster than using if's and else's, and allow more flexibility and design implementation. Imagine if you wanted to add more separate input functions? You would just have to make the function and set the function pointer in the one and that's it. Just some ideas out in the open. If you are intrested in FP here is a great site that I learned them from. Goodluck with your game!

Share on other sites
Hey, I'm experienced C++ coder and I know what function pointers are ;-)

What I'm going to do (since GUI code will appear only in not-game fragments) is to change one bool variable only when game mode changes from being in-game to menu (and vice-versa); that bool will control whether keys array is cleared:

if (mentioned_bool)
memset(actKeys, 0, keyCount);

Fighting for one little "if" in this case is really premature optimization :-]

Quote:

Thanks, that's really nice of you :-)

[EOT]

1. 1
Rutin
19
2. 2
JoeJ
15
3. 3
4. 4
5. 5

• 24
• 20
• 13
• 13
• 17
• Forum Statistics

• Total Topics
631700
• Total Posts
3001778
×