Files
jellyfin-desktop/src/input/InputSDL.cpp
T
Cory Parsons c254ed2b57 InputSDL: distinguish between repeat buttons and single shot buttons
This makes navigating the interface much smoother than it was, and
doesn't require external software to translate button presses into
keyboard commands.

Also added Support for xbox controller on Windows out of the box

Plex-CLA-1.0-signed-off-by: Cory Parsons <babylonstudio@gmail.com>
2015-10-28 09:23:58 +01:00

294 lines
8.2 KiB
C++

//
// InputSDL.cpp
// konvergo
//
// Created by Lionel CHAZALLON on 16/10/2014.
//
//
#include <QKeyEvent>
#include "InputSDL.h"
#include "QsLog.h"
#include <climits>
#include <cstdlib>
///////////////////////////////////////////////////////////////////////////////////////////////////
bool InputSDLWorker::initialize()
{
// close if it was previously inited
close();
// init SDL
if (SDL_Init(SDL_INIT_JOYSTICK) < 0)
{
QLOG_ERROR() << "SDL failed to initialize : " << SDL_GetError();
return false;
}
SDL_JoystickEventState(SDL_ENABLE);
refreshJoystickList();
return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
void InputSDLWorker::close()
{
if (SDL_WasInit(SDL_INIT_JOYSTICK))
{
QLOG_INFO() << "SDL is closing.";
// we need to close all the openned joysticks here and then exit the thread
for (int joyid = 0; joyid < m_joysticks.size(); joyid++)
{
SDL_JoystickClose(m_joysticks[joyid]);
}
SDL_Quit();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
QString InputSDLWorker::nameForId(SDL_JoystickID id)
{
if (m_joysticks.contains(id))
return SDL_JoystickName(m_joysticks[id]);
return "unknown joystick";
}
///////////////////////////////////////////////////////////////////////////////////////////////////
void InputSDLWorker::run()
{
QElapsedTimer polltimer;
QHash<int,int> repeats;
while (true)
{
SDL_Event event;
// startpoint for polling loop
polltimer.restart();
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
return;
break;
case SDL_JOYBUTTONDOWN:
{
QLOG_DEBUG() << "SDL Got button down for button #" << event.jbutton.button
<< " on Joystick #" << event.jbutton.which;
// emit that the button has gone down
emit receivedInput(nameForId(event.jbutton.which), QString("KEY_BUTTON_%1").arg(event.jbutton.button));
// set up the repeat timer for this button
QElapsedTimer* repeatTimer = new QElapsedTimer();
m_buttonTimestamps[event.jbutton.button] = repeatTimer;
repeatTimer->start();
// reset the repeat count for this button
repeats[event.jbutton.button] = 0;
break;
}
case SDL_JOYBUTTONUP:
{
if (m_buttonTimestamps[event.jbutton.button])
{
// remove the repeat timer for this button
delete m_buttonTimestamps[event.jbutton.button];
m_buttonTimestamps.remove(event.jbutton.button);
}
// reset the repeat count
repeats[event.jbutton.button] = 0;
break;
}
case SDL_JOYDEVICEADDED:
{
QLOG_INFO() << "SDL detected device was added.";
refreshJoystickList();
break;
}
case SDL_JOYDEVICEREMOVED:
{
QLOG_INFO() << "SDL detected device was removed.";
refreshJoystickList();
break;
}
case SDL_JOYAXISMOTION:
{
int deadband = 32768 * 0.15;
// handle the analog value push
// keep a dead band of 15%
if (std::abs(event.jaxis.value) > deadband)
{
// normalize the value
float normalizedvalue;
QString keyname;
if (event.jaxis.value > 0)
{
normalizedvalue = (float)(event.jaxis.value - deadband) / (float)(32767 - deadband);
keyname = QString("KEY_AXIS_%1_VAL_UP").arg(event.jaxis.axis);
}
else
{
normalizedvalue = (float)(std::abs(event.jaxis.value) - deadband) / (float)(32768 - deadband);
keyname = QString("KEY_AXIS_%1_VAL_DOWN").arg(event.jaxis.axis);
}
emit receivedInput(nameForId(event.jaxis.which), keyname, normalizedvalue);
}
// handle the Digital conversion of the analog axis
if (std::abs(event.jaxis.value) > 32768 / 2)
{
if (!m_axisState[event.jaxis.axis])
{
m_axisState[event.jaxis.axis] = 1;
if (event.jaxis.value > 0)
emit receivedInput(nameForId(event.jaxis.which), QString("KEY_AXIS_%1_UP").arg(event.jaxis.axis));
else
emit receivedInput(nameForId(event.jaxis.which), QString("KEY_AXIS_%1_DOWN").arg(event.jaxis.axis));
break;
}
}
m_axisState[event.jaxis.axis] = 0;
break;
}
}
}
// check for long press and repeats
SDLTimeStampMapIterator it = m_buttonTimestamps.constBegin();
while (it != m_buttonTimestamps.constEnd())
{
if (it.value())
{
// repeat at a rate of SDL_BUTTON_REPEAT_RATE
if (repeats[it.key()] > 0 && m_buttonTimestamps[it.key()]->elapsed() > SDL_BUTTON_REPEAT_RATE)
{
QLOG_DEBUG() << QString("SDL Got button repeat(%1) for button #").arg(repeats[it.key()]) << event.jbutton.button
<< " on Joystick #" << event.jbutton.which;
emit receivedInput(nameForId(event.jbutton.which), QString("KEY_BUTTON_%1").arg(it.key()));
m_buttonTimestamps[it.key()]->restart();
it = m_buttonTimestamps.constBegin();
repeats[it.key()]++;
continue;
}
else
{
// only start repeating if SDL_BUTTON_REPEAT_DELAY time has passed
if (m_buttonTimestamps[it.key()]->elapsed() > SDL_BUTTON_REPEAT_DELAY)
repeats[it.key()]++;
}
}
else
{
continue;
}
it++;
}
// make the poll time fixed to SDL_POLL_TIME
if (polltimer.elapsed() < SDL_POLL_TIME)
QThread::msleep(SDL_POLL_TIME - polltimer.elapsed());
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
void InputSDLWorker::refreshJoystickList()
{
// close all openned joysticks
SDLJoystickMapIterator it = m_joysticks.constBegin();
while (it != m_joysticks.constEnd())
{
if (SDL_JoystickGetAttached(m_joysticks[it.key()]))
SDL_JoystickClose(m_joysticks[it.key()]);
it++;
}
m_joysticks.clear();
m_buttonTimestamps.clear();
// list all the joysticks and open them
int numJoysticks = SDL_NumJoysticks();
QLOG_INFO() << "SDL found " << numJoysticks << " joysticks";
for (int joyid = 0; joyid < numJoysticks; joyid++)
{
SDL_Joystick* joystick = SDL_JoystickOpen(joyid);
if (joystick)
{
int instanceid = SDL_JoystickInstanceID(joystick);
QLOG_INFO() << "JoyStick #" << instanceid << " is " << SDL_JoystickName(joystick) << " with "
<< SDL_JoystickNumButtons(joystick) << " buttons and " << SDL_JoystickNumAxes(joystick)
<< "axes";
m_joysticks[instanceid] = joystick;
m_axisState.clear();
m_axisState.resize(SDL_JoystickNumAxes(joystick));
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
InputSDL::InputSDL(QObject* parent) : InputBase(parent)
{
m_thread = new QThread(this);
m_sdlworker = new InputSDLWorker(NULL);
m_sdlworker->moveToThread(m_thread);
connect(this, &InputSDL::run, m_sdlworker, &InputSDLWorker::run);
connect(m_sdlworker, &InputSDLWorker::receivedInput, this, &InputBase::receivedInput);
m_thread->start();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
InputSDL::~InputSDL()
{
close();
if (m_thread->isRunning())
{
m_thread->terminate();
m_thread->wait();
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
bool InputSDL::initInput()
{
bool retValue;
QMetaObject::invokeMethod(m_sdlworker, "initialize", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, retValue));
if (retValue)
emit run();
return retValue;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
void InputSDL::close()
{
QMetaObject::invokeMethod(m_sdlworker, "close");
}