Sign in to follow this  

SDL: No unicode information in key releases

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

If you intended to correct an error in the post then please contact us.

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 this post


Link to post
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 this post


Link to post
Share on other sites
Quote:
Original post by Koshmaar
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.


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.

Header File:

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 variable
Immortal_Input inp;
// Somewhere in the beginning of the update loop
inp.Update();
// Later in the update
if( 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 this post


Link to post
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 is
pretty easy to miss key presses and shift presses. People don't press
them at the same time so you might get wrong results at times. The only
way around that problem is to use a form of input that doesn't let you
miss 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 the
keyboard 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 only
way that I know of to catch keyboard state changes as they occur is to process
events and handle them appropriately.

Basically SDL_GetKeyState() gives you the current state after all events
have been processed, so if a key or button has been pressed and released
before you process events, then the pressed state will never show up in
the getstate calls.

Well, yes, but that won't solve your problem. The SDL keysyms only correspond
to unmodified keys on the keyboard. For example, the French keyboard
doesn't actually have number keys. :) To get '1' on a French keyboard, you
have to press shift and another key (I forget which), so you'll never get a
keyboard event with SDLK_1 in it on a French keyboard. If you enable UNICODE
translation though, you'll get an event with '1' in the unicode field when the
appropriate 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 this post


Link to 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 this post


Link to post
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 you
do.

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 the
key is down, so you will react accordingly in your input handling code as long
as it is not released. For example, if left is down, you will move player some
pixels to the left. When the key is released, you clear its state. That's what
you're doing in your code.

In a GUI context, whenever a key is pressed, you react to that event. If left is
pressed, you move cursor one char to the left. That's it. You don't need to
store anything or even handle SDL_KEYUP events. Of course, you need to use
SDL_EnableUNICODE() to get usuable infos. You will also use
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL), to
properly handle key repeat.

So, you need to switch from one mode to the other depending on the context. For
example when you see in your main game loop that user pressed, say, F12, then
you clear all key pressed, set a flag saying you're in GUI mode, and draw your
dialog box. Depending on the type of game (realtime or not), you will go to
another 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 this post


Link to post
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 this post


Link to post
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:
Goodluck with your game!


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



[EOT]

Share this post


Link to post
Share on other sites

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

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this