SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
SPDX-FileCopyrightText: 2023 Jeremy Whiting <jpwhiting@kde.org>
SPDX-FileCopyrightText: 2023 NiccolΓ² Venerandi <niccolo@venerandi.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "devicemodel.h"
#include <KLocalizedString>
#include <QTimer>
#include <SDL2/SDL.h>
#include <SDL2/SDL_joystick.h>
#include "gamepad.h"
#include "logging.h"
const int kShortPollTime = 100;
const int kLongPollTime = 2000;
static bool initialized = false;
DeviceModel::DeviceModel()
{
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &DeviceModel::poll);
m_timer->start(kLongPollTime);
QTimer::singleShot(kShortPollTime, this, &DeviceModel::poll);
}
DeviceModel::~DeviceModel()
{
if (initialized) {
qCDebug(KCM_GAMECONTROLLER) << "Calling SDL_Quit";
SDL_Quit();
initialized = false;
}
}
Gamepad *DeviceModel::device(int index) const
{
if (index < 0 || index >= m_devices.count())
return nullptr;
const int sdlIndex = m_devices.keys().at(index);
return m_devices.value(sdlIndex);
}
int DeviceModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_devices.count();
}
QVariant DeviceModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index)) {
return {};
}
if (role == Qt::DisplayRole) {
const int sdlIndex = m_devices.keys().at(index.row());
return i18nc("Device name and path", "%1 (%2)", m_devices.value(sdlIndex)->name(), m_devices.value(sdlIndex)->path());
}
return {};
}
void DeviceModel::poll()
{
if (!initialized) {
qCDebug(KCM_GAMECONTROLLER) << "Calling SDL_Init";
SDL_Init(SDL_INIT_GAMECONTROLLER);
initialized = true;
}
SDL_Event event{};
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_CONTROLLERDEVICEADDED:
addDevice(event.cdevice.which);
break;
case SDL_CONTROLLERDEVICEREMOVED:
removeDevice(event.cdevice.which);
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
m_devices.value(event.cbutton.which)->onButtonEvent(event.cbutton);
break;
case SDL_CONTROLLERAXISMOTION:
m_devices.value(event.caxis.which)->onAxisEvent(event.caxis);
break;
}
}
}
void DeviceModel::addDevice(const int deviceIndex)
{
const auto joystick = SDL_JoystickOpen(deviceIndex);
const auto id = SDL_JoystickInstanceID(joystick);
if (m_devices.contains(id)) {
qCWarning(KCM_GAMECONTROLLER) << "Got a duplicate add event, ignoring. Index: " << deviceIndex;
return;
}
const auto gamepad = SDL_GameControllerOpen(deviceIndex);
if (SDL_GameControllerTypeForIndex(deviceIndex) == SDL_CONTROLLER_TYPE_VIRTUAL) {
qCWarning(KCM_GAMECONTROLLER) << "Skipping gamepad since it is virtual. Index: " << deviceIndex;
return;
}
beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count());
m_devices.insert(id, new Gamepad(joystick, gamepad, this));
endInsertRows();
m_timer->setInterval(kShortPollTime);
Q_EMIT devicesChanged();
}
void DeviceModel::removeDevice(const int deviceIndex)
{
if (!m_devices.contains(deviceIndex)) {
qCWarning(KCM_GAMECONTROLLER) << "Invalid device index from removal event, ignoring";
return;
}
const int index = m_devices.keys().indexOf(deviceIndex);
beginRemoveRows(QModelIndex(), index, index);
m_devices.value(deviceIndex)->deleteLater();
m_devices.remove(deviceIndex);
endRemoveRows();
if (m_devices.count() == 0) {
m_timer->setInterval(kLongPollTime);
}
Q_EMIT devicesChanged();
}
int DeviceModel::count() const
{
return m_devices.size();
}
#include "moc_devicemodel.cpp"