[C/SDL] Need Ideas For Handling Keyboard Events

Status
Not open for further replies.
Level 15
Joined
Nov 30, 2007
Messages
1,202
There are a few methods to check key events: wait for event in a separate thread, event polling and finally key-state checks. All of these methods probably work pretty much in the same way behind the scenes, mainly checking for key-states and handling it accordingly.

This brings me to my problem, my keyboard sends press and release events when a key is held down, and i need advice on how to distinguish between human behavior and well, spam.

I have tried built in stuff like SDL_EnableKeyRepeat(0,0) which supposedly disables the repeat, Also tested the flag event.key.repeat, but that doesn't work as the events that are fired through holding in is hardware related. So that leaves me to accepting fluke events or trying to make a work around,

I'm thinking timestamps is the best way forward here, but then, don't I run the risk of mistaking real events for fake ones?



Any ideas? Is there a script that I could implement that could temporarily disable this for the user? Maybe I shouldn't ask at hive, but it's not the first forum I've asked on. ^^
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,285
This brings me to my problem, my keyboard sends press and release events when a key is held down, and i need advice on how to distinguish between human behavior and well, spam.

I have tried built in stuff like SDL_EnableKeyRepeat(0,0) which supposedly disables the repeat, Also tested the flag event.key.repeat, but that doesn't work as the events that are fired through holding in is hardware related. So that leaves me to accepting fluke events or trying to make a work around,
I think the behaviour is keyboard/driver specific. Some keyboards might physically convert a hold into a chain of presses.

As far as I am aware there is no real way to detect between actual user input and robot/macro user input. As far as your application goes they both appear legitimate coming in as input events. One can add flood control detection as well as supervisor applications to help detect when fowl play might be occurring however the results are never perfect. Many people who play Diablo III claim to have been unjustly banned for "macro abuse" when manually spamming keys for some of the stupid builds that were in use (eg chain explosive Wizard or Season 4 Hammerdin), however if they truly were playing fairly is always questionable.

The best way to deal with input abuse is to make spamming non advantageous. This technique was used by game developers to combat the use of auto fire controllers, they simply made it that auto fire cannot fire bullets faster than manual fire or that the mechanics need precise, human like timing rather than insane frequency.

You can also put some sort of reasonable limit, for example manually press keys for a minute at the fastest rate you can and count how many presses were registered. Round this number and multiply it by 2 (some people have insane skills apparently...) and convert to presses per second. Every second credit the user the number of key presses you calculated, up to some maximum limit. Every time they press a key deduct a press for their credit. If their credit drops below a a warning threshold warn them about input flooding. If it drops below a minimum threshold then you ignore all key presses until a certain positive threshold is reached.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
I'm much less worried about abuse as that is related to design, but it does good to keep your example in mind. No, im worried about not being able to open a chat window or multiple esc sequences being triggered making the controls unreliable.

My goal was to create API that the client would use to check for events like:

wasKeyBeingPressed(SDL_Scancode)
wasKeyBeingReleased(SDL_Scancode)
isKeyBeingHeld(SDL_Scancode)

But the tripplet spam my keyboard sends. (Pressed, pressed, released) makes it so all of the above get flagged in each cycle as having occurred.

The logic behind it was to compare the keystate from the previous cycle, but with the event spam all three things get detected as the logic behind it is.

The status of a key is flagged as true if its being pressed, which gave me this:

looping...i...
curStatus = getCurStatus(key);
sum = curStatus + prevStatus*2;
event = sum;

sum == 0 => nothing is happening. (0, 0)
sum == 1 => key was pressed (0, 1)
sum == 2 => key was released (1, 0)
sum == 3 => key is being held. (1, 1)


I had made a version using polling and there a certain delay had to pass until the key would count as released. But the same thing actually happens there to some extent. And after hitting on a bug - I wasn't sure if having delay was the proper way to go about it. The strangest part of this is that if i press down my key for a period of time, everything works fine. But once the key is released it starts to spam pressed, held released, for about an seemingly equal amount of time.

2 test runs to show what i mean here:
 

Attachments

  • output3.jpg
    output3.jpg
    150.5 KB · Views: 223
Last edited:
Level 29
Joined
Jul 29, 2007
Messages
5,174
As far as I remember, what I did in the past with SDL, was to have an array of all key states, use polling to set them, and then, after all events were processed, you can know the real state of your keys.

E.g. if you set the space (20) to true, false, true, false, true, false, and only then query your value, you know it is not pressed.

Super old code of mine:
C++:
std::map<sf::Keyboard::Key, bool> keys;

...

void handleEvents()
{
   sf::Event event;

   while (window.pollEvent(event))
   {
       if (event.type == sf::Event::Closed)
           window.close();

       if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
           window.close();

       if (event.type == sf::Event::KeyPressed)
       {
           keys[event.key.code] = true;
       }

       if (event.type == sf::Event::KeyReleased)
       {
           keys[event.key.code] = false;
       }
   }
}

If you need to know if a state changed, rather than only what it currently is, you can use a second map, and keep switching between them.
On frame 0, map A is current frame, map B is last frame.
On frame 1, map A is last frame, map B is current frame.
Etc., just keep switching the references.

Beyond this you can't really do anything, since system events are not in your control.
If this is a problem, you need to design your code to work differently, with your own events, that only indirectly happen as a response to system events (e.g. increment a number on every key pressed event, decrease every key released event, and only run an actual program action if it is bigger or equal to some N, and finally reset it back to 0).
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
The frame switching is what i currently use, I don't poll for it, i loop through the 250 or so available keycodes. Also if i switch to polling for the mapping, which might be better as far as optimization goes, but the issue then becomes that one cycle and what you think is one press, will in actuallity be pressed-released-pressed-released before the polling finishes, depending on your hardware of course. Not to mention other funny business that occurs...

Thanks for your code snippet, it looks much like all the other examples out there. It just doens't work fully for me for some reason.

I mean, your code works to find out if a key is being held down. But i run into the same trouble once i release it. It detects presses and releases that shouldn't be there.

JASS:
    void evRefresh()
    {
        SDL_Event e;
        keyboardState = SDL_GetKeyboardState(NULL);
        while(SDL_PollEvent(&e))
        {
            if (e.type == SDL_KEYDOWN)
                keyboardState[e.key.keysym.scancode] = true;
            else if (e.type == SDL_KEYUP)
                keyboardState[e.key.keysym.scancode] = false;
        }
        if (keyboardState[SDL_SCANCODE_ESCAPE])
            printf("Pressed");
        else
            printf("Released");
    }

The output is something like:

Released <--- Program starts
Released
Released
Released
Pressed <--- I start pressing.
Pressed
Pressed
Pressed
Released <--- I release
Pressed <--- Ghost presses found or something, odd behavior.
Released
Pressed
Released
...
Released <--- eventually back to normal.
Released
Released
...

I followed your advice regarding the up down counters and produced this hack job to check for fluke states, it seems to actually work somehow... But I don't know how reproduceable this is.
JASS:
void evRefresh()
    {
        SDL_Scancode k = SDL_SCANCODE_ESCAPE;
        SDL_Event e;
        keyboardState = SDL_GetKeyboardState(NULL);
        while(SDL_PollEvent(&e))
        {
            if (e.type == SDL_QUIT) {
                exit(0);
            }
            if (e.type == SDL_KEYDOWN ) {
                printf("pressed\n");
                keyboardState[e.key.keysym.scancode] = true;
                key_count[e.key.keysym.scancode]++;

            }
            else if (e.type == SDL_KEYUP) {
                printf("released\n");
                keyboardState[e.key.keysym.scancode] = false;
                key_count[e.key.keysym.scancode]--;
            }
        }

        if (key_count[k] == 1 && key_count_prev[k] == 0)
            printf("PRESSED!!!\n");
        else if ((key_count[k] > key_count_prev[k] && key_count[k] != 0) || (key_count[k] == key_count_prev[k] && key_count[k] > 0 ))
            printf("HELD!!!\n");
        else if (key_count[k] < key_count_prev[k]) {
            printf("RELEASED!!!\n");
            key_count[k] = 0;
        }
        if (key_count[k] == 1 && key_count_prev[k] == 0) {
            count_01_repeat[k]++;
            printf("01 --- %d\n", count_01_repeat[k]);
        }



        if (key_count[k] == 0 && key_count_prev[k] == 0)
            count_00_repeat[k]++;

        printf("00: %d\n", count_00_repeat[k]);

        if (count_00_repeat[k] > 0 && key_count[k] == 0 && !(key_count[k] < key_count_prev[k])) {
            printf("Back to normal?.\n");
            count_01_repeat[k] = 0;

        }


        if (count_01_repeat[k] > 1) {
            count_00_repeat[k] = 0;
            printf("This should be ignored.\n");
        }

        key_count_prev[k] = key_count[k];

        if (keyboardState[k])
            printf("{s} Pressed  --- %d\n",key_count[k]);
        else
            printf("{s} Released --- %d\n",key_count[k]);

        printf("--------------------------\n");
    }

Yes it's ugly and have some bugs. It works by looking for 01 repeats as invalid entries, 00 is the reset signal which informs that the bug stopped. Probably still breakable.


Something strange i've been noting is that clicking outside the SDL window seem to have some sort of debug effect, maybe this can be used. This is impossible, can't anticipate all possibilities...
 
Last edited:
Level 29
Joined
Jul 29, 2007
Messages
5,174
Oh, I copied that old code without even noticing it's SFML and not SDL, whoops.

Your code doesn't look correct to me, which might also explain why your "system" events make no sense.
Why would you ever get key-released events at startup, for example?
You are asking for the keyboard state from SDL, and then changing it, but SDL is also changing it in response to the system events you then poll.
Either you can rely on SDL's state, or make your own map, but right now both of you are mutating the same map.
See the remarks section SDL_GetKeyboardState - SDL Wiki'
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
Oh, I copied that old code without even noticing it's SFML and not SDL, whoops.

Your code doesn't look correct to me, which might also explain why your "system" events make no sense.
Why would you ever get key-released events at startup, for example?
You are asking for the keyboard state from SDL, and then changing it, but SDL is also changing it in response to the system events you then poll.
Either you can rely on SDL's state, or make your own map, but right now both of you are mutating the same map.
See the remarks section SDL_GetKeyboardState - SDL Wiki'

Often the need for calls to SDL_PumpEvents() is hidden from the user since SDL_PollEvent() and SDL_WaitEvent() implicitly call SDL_PumpEvents(). So polling all events actually updates the state array i think?

Also the second code, is a bit of a mess as it's me experimenting with finding a sequence that is "none-human" but i think it's a bad a aproach. The first one is a copy of yours, and the system output is the keyboard status result, not the events. (My bad). But it goes without saying if the status change from released to pressed, that is a fired event.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
You are basically given two choices - either let SDL handle the input events (SDL_GetKeyboardState), or do it yourself (your own map).
For some reason you are letting SDL fill its map, but then you go and change it.
Pick one or the other, because right now, like I said, both you and SDL are mutating the same map.

You will still get key repeats, but once your events are actually correct, you can handle repeating in all sorts of ways.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
Nice find!

How odd that the whole event system was completely bugged, and they managed to publish a new version.
Open source - usually your friend, sometimes less so. :p

You don't have any good suggestions on how to store whats on the screen. I gather you use rendercopy for reuplading them but basically all you need is a rect and a texture or is that specifically for text? (Preferably something i can canablize.)

Would you use a hashtable or something else for it?

I'm thinking the window needs to keep track of two kind of objects, text and images.
 
Last edited:
Level 29
Joined
Jul 29, 2007
Messages
5,174
I only had a tiny bit of experience with SDL's native surfaces.
At the time, they were software-rendered, and didn't support the most basic things (like rotations, or scaling), and you needed to gather shady 3rd party libraries to do these things for you.

I assume they are built directly on OpenGL nowadays, but I have no clue how the API looks, since I've never used SDL2.

I myself started using OpenGL very fast, because of said limitations, and never went back, so I can help you with that if you want, but not with any SDL-specific stuff.

For text in general, if you are fine with a monospaced font, you generally create a texture containing all of your characters (or images.google it, there are tons of them, in many languages), and then you texture-wrap rectangles to the letters.
For more advanced usages, you'd use a library like the FreeType Project, which I assume is integrated into SDL nowadays in some form or another.
But again, I can't help you with anything SDL-specific, my knowledge of SDL pertains to SDL1.2 from many years ago.
 
Status
Not open for further replies.
Top