Files
thunderpad/src/joypad.cpp
2025-06-30 17:15:11 -04:00

279 lines
8.5 KiB
C++

#include <QApplication>
#include "joypad.h"
//for actually interacting with the joystick devices
#include <linux/joystick.h>
#include <linux/input.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
JoyPad::JoyPad( int i, int dev, QObject *parent )
: QObject(parent), joydev(-1), axisCount(0), buttonCount(0), jpw(0), readNotifier(0), errorNotifier(0) {
debug_mesg("Constructing the joypad device with index %d and fd %d\n", i, dev);
//remember the index,
index = i;
//load data from the joystick device, if available.
if (dev >= 0) {
debug_mesg("Valid file handle, setting up handlers and reading axis configs...\n");
open(dev);
debug_mesg("done resetting and setting up device index %d\n", i);
} else {
debug_mesg("This joypad does not have a valid file handle, not setting up event listeners\n");
}
debug_mesg("Done constructing the joypad device %d\n", i);
}
JoyPad::~JoyPad() {
close();
}
void JoyPad::close() {
if (readNotifier) {
disconnect(readNotifier, 0, 0, 0);
readNotifier->blockSignals(true);
readNotifier->setEnabled(false);
delete readNotifier;
readNotifier = 0;
}
if (errorNotifier) {
disconnect(errorNotifier, 0, 0, 0);
errorNotifier->blockSignals(true);
errorNotifier->setEnabled(false);
delete errorNotifier;
errorNotifier = 0;
}
if (joydev >= 0) {
if (::close(joydev) != 0) {
debug_mesg("close(js%d %d): %s\n", index, joydev, strerror(errno));
}
joydev = -1;
}
}
void JoyPad::open(int dev) {
debug_mesg("resetting to dev\n");
//remember the device file descriptor
close();
joydev = dev;
char id[256];
memset(id, 0, sizeof(id));
if (ioctl(joydev, JSIOCGNAME(sizeof(id)), id) < 0) {
deviceId = "Unknown";
}
else {
deviceId = id;
}
//read in the number of axes / buttons
axisCount = 0;
ioctl (joydev, JSIOCGAXES, &axisCount);
buttonCount = 0;
ioctl (joydev, JSIOCGBUTTONS, &buttonCount);
//make sure that we have the axes we need.
//if one that we need doesn't yet exist, add it in.
//Note: if the current layout has a key assigned to an axis that did not
//have a real joystick axis mapped to it, and this function suddenly brings
//that axis into use, the key assignment will not be lost because the axis
//will already exist and no new axis will be created.
for (int i = axes.size(); i < axisCount; i++) {
axes.append(new Axis( i, this ));
}
for (int i = buttons.size(); i < buttonCount; i++) {
buttons.append(new Button( i, this ));
}
debug_mesg("Setting up joyDeviceListeners\n");
readNotifier = new QSocketNotifier(joydev, QSocketNotifier::Read, this);
connect(readNotifier, SIGNAL(activated(int)), this, SLOT(handleJoyEvents()));
errorNotifier = new QSocketNotifier(joydev, QSocketNotifier::Exception, this);
connect(errorNotifier, SIGNAL(activated(int)), this, SLOT(handleJoyEvents()));
debug_mesg("Done setting up joyDeviceListeners\n");
debug_mesg("done resetting to dev\n");
}
const QString &JoyPad::getDeviceId() const {
return deviceId;
}
QString JoyPad::getName() const {
return tr("Joystick %1 (%2)").arg(index+1).arg(deviceId);
}
int JoyPad::getIndex() const {
return index;
}
void JoyPad::toDefault() {
//to reset the whole, reset all the parts.
foreach (Axis *axis, axes) {
axis->toDefault();
}
foreach (Button *button, buttons) {
button->toDefault();
}
}
bool JoyPad::isDefault() {
//if any of the parts are not at default, then the whole isn't either.
foreach (Axis *axis, axes) {
if (!axis->isDefault()) return false;
}
foreach (Button *button, buttons) {
if (!button->isDefault()) return false;
}
return true;
}
bool JoyPad::readConfig( QTextStream &stream ) {
toDefault();
QString word;
QChar ch = QChar(0);
int num = 0;
stream >> word;
while (!word.isNull() && word != "}") {
word = word.toLower();
if (word == "button") {
stream >> num;
if (num > 0) {
stream >> ch;
if (ch != ':') {
errorBox(tr("Layout file error"), tr("Expected ':', found '%1'.").arg(ch));
return false;
}
for (int i = buttons.size(); i < num; ++ i) {
buttons.append(new Button(i, this));
}
if (!buttons[num-1]->read( stream )) {
errorBox(tr("Layout file error"), tr("Error reading Button %1").arg(num));
return false;
}
}
else {
stream.readLine();
}
}
else if (word == "axis") {
stream >> num;
if (num > 0) {
stream >> ch;
if (ch != ':') {
errorBox(tr("Layout file error"), tr("Expected ':', found '%1'.").arg(ch));
return false;
}
for (int i = axes.size(); i < num; ++ i) {
axes.append(new Axis(i, this));
}
if (!axes[num-1]->read(stream)) {
errorBox(tr("Layout file error"), tr("Error reading Axis %1").arg(num));
return false;
}
}
}
else {
errorBox(tr("Layout file error"), tr("Error while reading layout. Unrecognized word: %1").arg(word));
return false;
}
stream >> word;
}
return true;
}
//only actually writes something if this JoyPad is NON DEFAULT.
void JoyPad::write( QTextStream &stream ) {
if (!axes.empty() || !buttons.empty()) {
stream << "Joystick " << (index+1) << " {\n";
foreach (Axis *axis, axes) {
if (!axis->isDefault()) {
axis->write(stream);
}
}
foreach (Button *button, buttons) {
if (!button->isDefault()) {
button->write(stream);
}
}
stream << "}\n\n";
}
}
void JoyPad::release() {
foreach (Axis *axis, axes) {
axis->release();
}
foreach (Button *button, buttons) {
button->release();
}
}
void JoyPad::jsevent(const js_event &msg) {
//if there is a JoyPadWidget around, ie, if the joypad is being edited
if (jpw != NULL && hasFocus) {
//tell the dialog there was an event. It will use this to flash
//the appropriate button, if necesary.
jpw->jsevent(msg);
return;
}
//if the dialog is open, stop here. We don't want to signal ourselves with
//the input we generate.
if (qApp->activeWindow() != 0 && qApp->activeModalWidget() != 0) return;
//otherwise, lets create us a fake event! Pass on the event to whichever
//Button or Axis was pressed and let them decide what to do with it.
unsigned int type = msg.type & ~JS_EVENT_INIT;
if (type == JS_EVENT_AXIS) {
debug_mesg("DEBUG: passing on an axis event\n");
debug_mesg("DEBUG: %d %d\n", msg.number, msg.value);
if (msg.number < axes.size()) axes[msg.number]->jsevent(msg.value);
else debug_mesg("DEBUG: axis index out of range: %d\n", msg.value);
}
else if (type == JS_EVENT_BUTTON) {
debug_mesg("DEBUG: passing on a button event\n");
debug_mesg("DEBUG: %d %d\n", msg.number, msg.value);
if (msg.number < buttons.size()) buttons[msg.number]->jsevent(msg.value);
else debug_mesg("DEBUG: button index out of range: %d\n", msg.value);
}
}
JoyPadWidget* JoyPad::widget( QWidget* parent, int i) {
//create the widget and remember it.
jpw = new JoyPadWidget(this, i, parent);
return jpw;
}
void JoyPad::handleJoyEvents() {
js_event msg;
ssize_t len = read(joydev, &msg, sizeof(js_event));
//if there was a real event waiting,
if (len == sizeof(js_event)) {
//pass that event on to the joypad!
jsevent(msg);
}
}
void JoyPad::releaseWidget() {
//this is how we know that there is no longer a JoyPadWidget around.
jpw = 0;
}
void JoyPad::errorRead() {
debug_mesg("There was an error reading off of the device with fd %d, disabling\n", joydev);
close();
debug_mesg("Done disabling device with fd %d\n", joydev);
}
void JoyPad::focusChange(bool focusState) {
hasFocus = !focusState;
}