Categories
Development

SDL2 cannot capture keyboard events c++, ubuntu 1804

TL;DR

I am trying to capture keyboard events (more specifically, the Ctrl+c command) in my own C++ program. I am attempting this through generic keyboard presses in SDL2.

END TL;DR

I have found links on SO and the internet that cover the subject of handling keyboard events with SDL2. I have a few of them listed here.

https://stackoverflow.com/questions/28105533/sdl2-joystick-dont-capture-pressed-event
https://lazyfoo.net/tutorials/SDL/04_key_presses/index.php
http://www.cplusplus.com/forum/windows/182214/
Handling Keyboard and Mouse Events in SDL2

The major issue I think is causing the problem is that I am also using an Xbox-style joystick at the same time. I have had no issues whatsoever with capturing joystick events. I have been doing that for a long time now. I am having issues trying to get anything with the keyboard to throw an event. I have tried if(event.type == SDL_KEYDOWN) and then checking which key it was, but that appears to return nothing. I feel like there is some macro that I need to define to allow this since I keep finding the same solutions on the internet.

I have included the entire script that I am running at the moment.


#include <boost/thread.hpp>
// Time library
#include <chrono>
// vector data structure
#include <vector>
// Thread-safe base variables
#include <atomic>
// std::cout
#include <iostream>
// Joystick library
#include <SDL2/SDL.h>
// Counters for printing
std::atomic_int printcounter{ 0 };
// This is every 3 * 1000 milliseconds
const int printer = 300;
// If an event is found, allow for printing.
std::atomic_bool eventupdate{ false };
// This function converts the raw joystick axis from the SDL library to proper double precision floating-point values.
double intToDouble(int input)
{
    return (double) input / 32767.0 ;
}
// Prevent joystick values from going outside the physical limit
double clamp(double input)
{
    return (input < -1.0) ? -1.0 : ( (input > 1.0) ? 1.0 : input);
}
// SDL library joystick deadband
const int JOYSTICK_DEAD_ZONE = 5000;
// These are the raw read in values from the joystick in XInput (XBox) mode.
//Normalized direction
int leftX = 0;
int leftY = 0;
int rightX = 0;
int rightY = 0;
int leftTrigger  = -32768;
int rightTrigger = -32768;
// Button array
uint buttons[11] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// Tbe pov hat is only 4 bits - 1, 2, 4, 8
int povhat = 0;
// These are the rectified joystick values
double leftstickx = 0;
double leftsticky = 0;
double rightstickx = 0;
double rightsticky = 0;
double lefttrigger = 0;
double righttrigger = 0;
// These are the rectified boolean buttons
bool leftstickbut = false;
bool rightstickbut = false;
bool xbutton = false;
bool ybutton = false;
bool abutton = false;
bool bbutton = false;
bool rightbut = false;
bool leftbut = false;
bool startbut = false;
bool backbut = false;
bool centbut = false;

// This is the boolean that controls running the robot.
std::atomic_bool quitrobot{false};
// Joystick values
static double joyvalues[6] = { 0, 0, 0, 0, 0, 0};
static bool joybuttons[11] = { false };
// Sleep function
void wait(int milliseconds)
{
    boost::this_thread::sleep_for(boost::chrono::milliseconds{milliseconds});
}
// Now the main code
int main(int argc, char** argv)
{
    // Now the robot goes through the looping code until a quit flag is set to true
    while ( ! quitrobot)
    {
        // Now we look for an Xbox-style joystick
        std::cout << "Looking for gamepad..." << std::endl;
        while(true)
        {
            // Now the program waits until an Xbox-style joystick is plugged in.
            // resetting SDL makes things more stable
            SDL_Quit();
            // restart SDL with the expectation that a jostick is required.
            SDL_Init(SDL_INIT_JOYSTICK);
            // SDL_HINT_GRAB_KEYBOARD
            // check for a joystick
            int res = SDL_NumJoysticks();
            if (res > 0) { break; } // Here a joystick has been detected.
            if (res < 0)
            {
                std::cout << "Joystick detection error: " << std::to_string(res) << std::endl;
            }
            // we don't want the program running super fast when detecting hardware.
            wait(20);
        }
        // Now we check to make sure that the joystick is valid.
        // Open the joystick for reading and store its handle in the joy variable
        SDL_Joystick *joy = SDL_JoystickOpen(0);
        if (joy == NULL) {
            /* back to top of while loop */
            continue;
        }
        // Get information about the joystick
        const char *name = SDL_JoystickName(joy);
        const int num_axes = SDL_JoystickNumAxes(joy);
        const int num_buttons = SDL_JoystickNumButtons(joy);
        const int num_hats = SDL_JoystickNumHats(joy);
        printf("Now reading from joystick '%s' with:\n"
            "%d axes\n"
            "%d buttons\n"
            "%d hats\n\n",
            name,
            num_axes,
            num_buttons,
            num_hats);
        /* I'm using a logitech F350 wireless in X mode.
        If num axis is 4, then gamepad is in D mode, so neutral drive and wait for X mode.
        [SAFETY] This means 'D' becomes our robot-disable button.
        This can be removed if that's not the goal. */
        if (num_axes < 5) {
            /* back to top of while loop */
            continue;
        }
        // This is the read joystick and drive robot loop.
        while(true)
        {
            // poll for disconnects or bad things
            SDL_Event e;
            if (SDL_PollEvent(&e)) {
                // SDL generated quit command
                if (e.type == SDL_QUIT) { break; }
                // Checking for Ctrl+c on the keyboard
                // SDL_Keymod modstates = SDL_GetModState();
                // if (modstates & KMOD_CTRL)
                // {
                    // One of the Ctrl keys are being held down
                    // std::cout << "Pressed Ctrl key." << std::endl;
                // }
                if(e.key.keysym.scancode == SDLK_RCTRL || e.key.keysym.scancode == SDLK_LCTRL || SDL_SCANCODE_RCTRL == e.key.keysym.scancode || e.key.keysym.scancode == SDL_SCANCODE_LCTRL)
                {
                    std::cout << "Pressed QQQQ." << std::endl;
                }
                if (e.type == SDL_KEYDOWN)
                {
                    switch(e.key.keysym.sym){ 
                        case SDLK_UP: 
                            std::cout << "Pressed up." << std::endl;
                            break; 
                        case SDLK_RCTRL: 
                            std::cout << "Pressed up." << std::endl;
                            break; 
                        case SDLK_LCTRL: 
                            std::cout << "Pressed up." << std::endl;
                            break; 
                    }
                    // Select surfaces based on key press
                    switch( e.key.keysym.sym )
                    {
                        case SDLK_UP:
                            std::cout << "Pressed Up." << std::endl;
                        break;

                        case SDLK_DOWN:
                            std::cout << "Pressed Up." << std::endl;
                        break;

                        case SDLK_LEFT:
                            std::cout << "Pressed Up." << std::endl;
                        break;

                        case SDLK_RIGHT:
                            std::cout << "Pressed Up." << std::endl;
                        break;
                    }
                    std::cout << "Pressed blah di blah blah please print me." << std::endl;
                }
                // Checking which joystick event occured
                if (e.jdevice.type == SDL_JOYDEVICEREMOVED) { break; }
                // Since joystick is not erroring out, we can 
                else if( e.type == SDL_JOYAXISMOTION )
                {
                    //Motion on controller 0
                    if( e.jaxis.which == 0 )
                    {
                        // event happened
                        eventupdate = true;
                        // Left X axis 
                        if( e.jaxis.axis == 0 )
                        {
                            // dead zone check
                            if( e.jaxis.value > -JOYSTICK_DEAD_ZONE && e.jaxis.value < JOYSTICK_DEAD_ZONE)
                            {
                                leftX = 0;
                            }
                            else
                            {
                                leftX = e.jaxis.value;
                            }
                        }
                        // Right Y axis
                        else if( e.jaxis.axis == 1 )
                        {
                            // dead zone check
                            if( e.jaxis.value > -JOYSTICK_DEAD_ZONE && e.jaxis.value < JOYSTICK_DEAD_ZONE)
                            {
                                leftY = 0;
                            }
                            else
                            {
                                leftY = e.jaxis.value;
                            }
                        }
                        // Left trigger
                        else if ( e.jaxis.axis == 2 )
                        {
                            // dead zone check
                            if( e.jaxis.value > -JOYSTICK_DEAD_ZONE && e.jaxis.value < JOYSTICK_DEAD_ZONE)
                            {
                                leftTrigger = 0;
                            }
                            else
                            {
                                leftTrigger = e.jaxis.value;
                            }
                        }
                        // Right X axis
                        else if( e.jaxis.axis == 3 )
                        {
                            // dead zone check
                            if( e.jaxis.value > -JOYSTICK_DEAD_ZONE && e.jaxis.value < JOYSTICK_DEAD_ZONE)
                            {
                                rightX = 0;
                            }
                            else
                            {
                                rightX = e.jaxis.value;
                            }
                        }
                        // Right Y axis
                        else if( e.jaxis.axis == 4 )
                        {
                            // dead zone check
                            if( e.jaxis.value > -JOYSTICK_DEAD_ZONE && e.jaxis.value < JOYSTICK_DEAD_ZONE)
                            {
                                rightY = 0;
                            }
                            else
                            {
                                rightY = e.jaxis.value;
                            }
                        }
                        // Right trigger
                        else if( e.jaxis.axis == 5 )
                        {
                            // dead zone check
                            if( e.jaxis.value > -JOYSTICK_DEAD_ZONE && e.jaxis.value < JOYSTICK_DEAD_ZONE)
                            {
                                rightTrigger = 0;
                            }
                            else
                            {
                                rightTrigger = e.jaxis.value;
                            }
                        }
                    }
                }
                else if ( e.type == SDL_JOYBUTTONUP || e.type == SDL_JOYBUTTONDOWN )
                {

                    // now we are looking for button events.
                    if (e.jbutton.which == 0)
                    {
                        // event happened
                        eventupdate = true;
                        // buttons[e.jbutton.button] = e.jbutton.state;
                        if (e.jbutton.button == 0)
                        {
                            buttons[0] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 1)
                        {
                            buttons[1] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 2)
                        {
                            buttons[2] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 3)
                        {
                            buttons[3] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 4)
                        {
                            buttons[4] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 5)
                        {
                            buttons[5] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 6)
                        {
                            buttons[6] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 7)
                        {
                            buttons[7] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 8)
                        {
                            buttons[8] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 9)
                        {
                            buttons[9] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 10)
                        {
                            buttons[10] = e.jbutton.state;
                        }
                        if (e.jbutton.button == 11)
                        {
                            buttons[11] = e.jbutton.state;
                        }
                    }
                }
                else if ( e.type == SDL_JOYHATMOTION)
                {
                    if (e.jhat.which == 0)
                    {
                        // event happened
                        eventupdate = true;
                        povhat = e.jhat.value;
                    }
                }
            }

            // Now that we have read in the values directly from the joystick we need top convert the values properly.
            leftstickx = clamp(intToDouble(leftX));
            leftsticky = clamp(intToDouble(leftY));
            rightstickx = clamp(intToDouble(rightX));
            rightsticky = clamp(intToDouble(rightY));
            lefttrigger = clamp(intToDouble(leftTrigger));
            righttrigger = clamp(intToDouble(rightTrigger));
            // rectify the buttons to become boolean values instead of integers.
            abutton = buttons[0] > 0;
            bbutton = buttons[1] > 0;
            xbutton = buttons[2] > 0;
            ybutton = buttons[3] > 0;
            //
            rightbut = buttons[4] > 0;
            leftbut  = buttons[5] > 0;
            //
            centbut = buttons[8] > 0;
            startbut = buttons[7] > 0;
            backbut = buttons[6] > 0;
            //
            leftstickbut = buttons[9] > 0;
            rightstickbut = buttons[10] > 0;

            // Transfer axis to the array.
            joyvalues[0] = leftstickx;
            joyvalues[1] = leftsticky;
            joyvalues[2] = rightstickx;
            joyvalues[3] = rightsticky;
            joyvalues[4] = lefttrigger;
            joyvalues[5] = righttrigger;
            // We are using the "B" button to quit the program
            if (bbutton)
            {
                quitrobot = true;
                std::cout << "Shutting down program." << std::endl;
                break;
            }
            if (eventupdate)                    
            {
                // This section of code is meant for running code that happens when SDL has detected an event.
                // This code section can be used for something else as well.
                if (e.key.keysym.sym == SDL_SCANCODE_RCTRL || e.key.keysym.sym == SDL_SCANCODE_LCTRL || e.key.keysym.sym == SDLK_LCTRL || e.key.keysym.sym == SDLK_RCTRL)
                {
                    std::cout << "SDL Event: Ctrl pressed.\n" << std::endl;
                }
                // Simply print the event
                eventupdate = false;
            } else {}
            if ( ! (printcounter = ((printcounter + 1) % printer)))
            {
                // const Uint8 *state = SDL_GetKeyboardState(NULL);
                // if (state[SDL_SCANCODE_RETURN]) {
                //     printf("<RETURN> is pressed.\n");
                // }
            }
            // Sleep the program for a short bit
            wait(5);
        }
        // Reset SDL since the robot is no longer being actuated.
        SDL_JoystickClose(joy);
        // We get here only if the joystick has been disconnected.
        std::cout << "Gamepad disconnected.\n" << std::endl;
    }
    // The program then completes. 
    return 0;
}



The most important part of that huge block of code is lines 129 to 179. I was doing more fooling around trying to get key capture to work but I could not get a response. Everywhere else is logic for the joystick reading (which has worked for me flawlessly). I have been referring to this link for all of the macros available to the programmer. I have not been able to capture the left control button or the right control button. I have also been trying the arrow keys for kicks as well and those are not working either. I know there are remnants of other code snippets thrown in there as I was testing. Given all of my testing, I am just not sure how to capture any keyboard keys, let alone Ctrl+c. None of my desired print statements print.

I am able to run the code on Ubuntu 1804 LTS with the stock GUI manager and window manager. I have a feeling the problem might also have something to do with the operating system not letting SDL2 capture the keyboard, but I don’t know what to do to allow only the keyboard or certain keys to be consumed by SDL2.

I am trying to not use platform-specific code since I already have successfully used platform-specific signal interrupts. My goal is to simply make a certain combination of depressed keys result in a program terminating. I figured that, since SDL2 can access all keys on a keyboard, that I should be able to simply

Leave a Reply

Your email address will not be published. Required fields are marked *