Use QGamepad in QApple.

This requires an interface as napple uses libevdev.


Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
This commit is contained in:
Andrea Odetti 2017-10-06 20:48:14 +01:00
parent 6ec2408e44
commit 348ba1ecd9
15 changed files with 381 additions and 192 deletions

View file

@ -27,7 +27,7 @@ add_library(appleii SHARED
source/linux/wwrapper.cpp source/linux/wwrapper.cpp
source/linux/state.cpp source/linux/state.cpp
source/linux/benchmark.cpp source/linux/benchmark.cpp
source/linux/joy_input.cpp source/linux/paddle.cpp
source/Z80VICE/z80.cpp source/Z80VICE/z80.cpp
source/Z80VICE/z80mem.cpp source/Z80VICE/z80mem.cpp
@ -44,6 +44,7 @@ add_executable(applen
source/frontends/ncurses/main.cpp source/frontends/ncurses/main.cpp
source/frontends/ncurses/world.cpp source/frontends/ncurses/world.cpp
source/frontends/ncurses/colors.cpp source/frontends/ncurses/colors.cpp
source/frontends/ncurses/evdevpaddle.cpp
source/frontends/ncurses/nframe.cpp source/frontends/ncurses/nframe.cpp
source/frontends/ncurses/asciiart.cpp source/frontends/ncurses/asciiart.cpp
source/frontends/ncurses/resources.cpp source/frontends/ncurses/resources.cpp

View file

@ -0,0 +1,107 @@
#include "StdAfx.h"
#include "frontends/ncurses/evdevpaddle.h"
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <libevdev/libevdev.h>
#include "Log.h"
EvDevPaddle::EvDevPaddle(const std::string & device)
: myButtonCodes(2), myAxisCodes(2), myAxisMins(2), myAxisMaxs(2)
{
myFD = open(device.c_str(), O_RDONLY | O_NONBLOCK);
if (myFD > 0)
{
libevdev * dev;
int rc = libevdev_new_from_fd(myFD, &dev);
if (rc < 0)
{
LogFileOutput("Input: failed to init libevdev (%s): %s\n", strerror(-rc), device.c_str());
}
else
{
myDev.reset(dev, libevdev_free);
myName = libevdev_get_name(dev);
myButtonCodes[0] = BTN_SOUTH;
myButtonCodes[1] = BTN_EAST;
myAxisCodes[0] = ABS_X;
myAxisCodes[1] = ABS_Y;
for (size_t i = 0; i < myAxisCodes.size(); ++i)
{
myAxisMins[i] = libevdev_get_abs_minimum(dev, myAxisCodes[i]);
myAxisMaxs[i] = libevdev_get_abs_maximum(dev, myAxisCodes[i]);
}
}
}
else
{
LogFileOutput("Input: failed to open device (%s): %s\n", strerror(errno), device.c_str());
}
}
EvDevPaddle::~EvDevPaddle()
{
if (myFD > 0)
{
close(myFD);
}
}
int EvDevPaddle::poll()
{
int counter = 0;
if (!myDev)
{
return counter;
}
input_event ev;
int rc = LIBEVDEV_READ_STATUS_SUCCESS;
do
{
if (rc == LIBEVDEV_READ_STATUS_SYNC)
rc = libevdev_next_event(myDev.get(), LIBEVDEV_READ_FLAG_SYNC, &ev);
else
rc = libevdev_next_event(myDev.get(), LIBEVDEV_READ_FLAG_NORMAL, &ev);
++counter;
} while (rc >= 0);
return counter;
}
const std::string & EvDevPaddle::getName() const
{
return myName;
}
bool EvDevPaddle::getButton(int i) const
{
int value = 0;
if (myDev)
{
int rc = libevdev_fetch_event_value(myDev.get(), EV_KEY, myButtonCodes[i], &value);
}
return value != 0;
}
int EvDevPaddle::getAxis(int i) const
{
if (myDev)
{
int value = 0;
int rc = libevdev_fetch_event_value(myDev.get(), EV_ABS, myAxisCodes[i], &value);
int pdl = 255 * (value - myAxisMins[i]) / (myAxisMaxs[i] - myAxisMins[i]);
return pdl;
}
else
{
return 0;
}
}

View file

@ -1,26 +1,24 @@
#pragma once #pragma once
#include "linux/paddle.h"
#include <string> #include <string>
#include <memory>
#include <vector> #include <vector>
struct libevdev; struct libevdev;
struct input_event; struct input_event;
class Input class EvDevPaddle : public Paddle
{ {
public: public:
Input(const std::string & device); EvDevPaddle(const std::string & device);
~Input(); ~EvDevPaddle();
int poll(); int poll();
bool getButton(int i) const; const std::string & getName() const;
int getAxis(int i) const; virtual bool getButton(int i) const;
virtual int getAxis(int i) const;
static void initialise(const std::string & device);
static Input & instance();
private: private:
int myFD; int myFD;
@ -28,10 +26,10 @@ private:
void process(const input_event & ev); void process(const input_event & ev);
std::string myName;
std::vector<unsigned int> myButtonCodes; std::vector<unsigned int> myButtonCodes;
std::vector<unsigned int> myAxisCodes; std::vector<unsigned int> myAxisCodes;
std::vector<int> myAxisMins; std::vector<int> myAxisMins;
std::vector<int> myAxisMaxs; std::vector<int> myAxisMaxs;
static std::shared_ptr<Input> ourSingleton;
}; };

View file

@ -13,11 +13,12 @@
#include "Memory.h" #include "Memory.h"
#include "linux/interface.h" #include "linux/interface.h"
#include "linux/joy_input.h" #include "linux/paddle.h"
#include "frontends/ncurses/nframe.h" #include "frontends/ncurses/nframe.h"
#include "frontends/ncurses/colors.h" #include "frontends/ncurses/colors.h"
#include "frontends/ncurses/asciiart.h" #include "frontends/ncurses/asciiart.h"
#include "frontends/ncurses/evdevpaddle.h"
namespace namespace
{ {
@ -25,6 +26,7 @@ namespace
std::shared_ptr<Frame> frame; std::shared_ptr<Frame> frame;
std::shared_ptr<GraphicsColors> colors; std::shared_ptr<GraphicsColors> colors;
std::shared_ptr<ASCIIArt> asciiArt; std::shared_ptr<ASCIIArt> asciiArt;
std::shared_ptr<EvDevPaddle> paddle;
int g_nTrackDrive1 = -1; int g_nTrackDrive1 = -1;
int g_nTrackDrive2 = -1; int g_nTrackDrive2 = -1;
@ -372,7 +374,10 @@ void VideoInitialize()
frame.reset(new Frame()); frame.reset(new Frame());
asciiArt.reset(new ASCIIArt()); asciiArt.reset(new ASCIIArt());
Input::initialise("/dev/input/by-id/usb-©Microsoft_Corporation_Controller_1BBE3DB-event-joystick");
paddle.reset(new EvDevPaddle("/dev/input/by-id/usb-©Microsoft_Corporation_Controller_1BBE3DB-event-joystick"));
Paddle::instance() = paddle;
signal(SIGINT, sig_handler); signal(SIGINT, sig_handler);
} }
@ -515,7 +520,7 @@ int ProcessKeyboard()
void ProcessInput() void ProcessInput()
{ {
Input::instance().poll(); paddle->poll();
} }
BYTE KeybGetKeycode () BYTE KeybGetKeycode ()

View file

@ -0,0 +1,38 @@
#include "gamepadpaddle.h"
GamepadPaddle::GamepadPaddle(const std::shared_ptr<QGamepad> & gamepad) : myGamepad(gamepad)
{
}
bool GamepadPaddle::getButton(int i) const
{
switch (i)
{
case 0:
return myGamepad->buttonA();
case 1:
return myGamepad->buttonB();
default:
return 0;
}
}
int GamepadPaddle::getAxis(int i) const
{
double value;
switch (i)
{
case 0:
value = myGamepad->axisLeftX();
break;
case 1:
value = myGamepad->axisLeftY();
break;
default:
value = 0.0;
}
const int pdl = int((value + 1.0) / 2.0 * 255.0);
return pdl;
}

View file

@ -0,0 +1,20 @@
#ifndef GAMEPADPADDLE_H
#define GAMEPADPADDLE_H
#include <QGamepad>
#include "linux/paddle.h"
class GamepadPaddle : public Paddle
{
public:
GamepadPaddle(const std::shared_ptr<QGamepad> & gamepad);
virtual bool getButton(int i) const;
virtual int getAxis(int i) const;
private:
const std::shared_ptr<QGamepad> myGamepad;
};
#endif // GAMEPADPADDLE_H

View file

@ -1,5 +1,6 @@
#include "preferences.h" #include "preferences.h"
#include <QFileDialog> #include <QFileDialog>
#include <QtGamepad/QGamepad>
namespace namespace
{ {
@ -105,6 +106,32 @@ Preferences::Preferences(QWidget *parent) :
myHDs.push_back(hd2); myHDs.push_back(hd2);
} }
void Preferences::setup(const Data & data, const boost::property_tree::ptree & registry)
{
populateJoysticks();
setData(data);
setRegistry(registry);
}
void Preferences::populateJoysticks()
{
joystick->clear();
const QList<int> gamepads = QGamepadManager::instance()->connectedGamepads();
joystick->addItem("None");
for (int id : gamepads)
{
QGamepad gp(id);
QString name = gp.name();
if (name.isEmpty())
{
name = QString::number(id);
}
joystick->addItem(name, QVariant::fromValue(id));
}
}
void Preferences::setRegistry(const boost::property_tree::ptree & registry) void Preferences::setRegistry(const boost::property_tree::ptree & registry)
{ {
registryTree->clear(); registryTree->clear();
@ -126,6 +153,8 @@ void Preferences::setData(const Data & data)
// synchronise // synchronise
on_hd_7_clicked(data.hdInSlot7); on_hd_7_clicked(data.hdInSlot7);
joystick->setCurrentText(data.joystick);
} }
Preferences::Data Preferences::getData() const Preferences::Data Preferences::getData() const
@ -139,6 +168,13 @@ Preferences::Data Preferences::getData() const
data.mouseInSlot4 = mouse_4->isChecked(); data.mouseInSlot4 = mouse_4->isChecked();
data.cpmInSlot5 = cpm_5->isChecked(); data.cpmInSlot5 = cpm_5->isChecked();
data.hdInSlot7 = hd_7->isChecked(); data.hdInSlot7 = hd_7->isChecked();
data.joystick = joystick->currentText();
if (joystick->currentIndex() >= 1)
{
const QVariant & device = joystick->itemData(joystick->currentIndex());
data.joystickId = device.toInt();
}
return data; return data;
} }

View file

@ -18,14 +18,17 @@ public:
bool mouseInSlot4; bool mouseInSlot4;
bool cpmInSlot5; bool cpmInSlot5;
bool hdInSlot7; bool hdInSlot7;
QString joystick;
int joystickId; // only putput
std::vector<QString> disks; std::vector<QString> disks;
std::vector<QString> hds; std::vector<QString> hds;
}; };
explicit Preferences(QWidget *parent); explicit Preferences(QWidget *parent);
void setRegistry(const boost::property_tree::ptree & registry); void setup(const Data & data, const boost::property_tree::ptree & registry);
void setData(const Data & data);
Data getData() const; Data getData() const;
private slots: private slots:
@ -45,6 +48,9 @@ private:
std::vector<QComboBox *> myDisks; std::vector<QComboBox *> myDisks;
std::vector<QComboBox *> myHDs; std::vector<QComboBox *> myHDs;
void setRegistry(const boost::property_tree::ptree & registry);
void setData(const Data & data);
void populateJoysticks();
void browseDisk(const std::vector<QComboBox *> & disks, const size_t id); void browseDisk(const std::vector<QComboBox *> & disks, const size_t id);
}; };

View file

@ -145,6 +145,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Joystick</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="joystick"/>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View file

@ -17,10 +17,11 @@
#include "linux/data.h" #include "linux/data.h"
#include "linux/configuration.h" #include "linux/configuration.h"
#include "linux/benchmark.h" #include "linux/benchmark.h"
#include "linux/joy_input.h" #include "linux/paddle.h"
#include "emulator.h" #include "emulator.h"
#include "memorycontainer.h" #include "memorycontainer.h"
#include "gamepadpaddle.h"
#include <QMdiSubWindow> #include <QMdiSubWindow>
#include <QMessageBox> #include <QMessageBox>
@ -34,7 +35,6 @@ namespace
setbuf(g_fh, NULL); setbuf(g_fh, NULL);
InitializeRegistry("../qapple/applen.conf"); InitializeRegistry("../qapple/applen.conf");
Input::initialise("/dev/input/by-id/usb-©Microsoft_Corporation_Controller_1BBE3DB-event-joystick");
LogFileOutput("Initialisation\n"); LogFileOutput("Initialisation\n");
@ -206,7 +206,7 @@ void QApple::on_timer()
g_dwCyclesThisFrame -= dwClksPerFrame; g_dwCyclesThisFrame -= dwClksPerFrame;
myEmulator->redrawScreen(); myEmulator->redrawScreen();
} }
Input::instance().poll(); //Input::instance().poll();
} }
while (DiskIsSpinning()); while (DiskIsSpinning());
} }
@ -314,8 +314,13 @@ void QApple::on_actionOptions_triggered()
currentOptions.apple2Type = getApple2ComputerType(); currentOptions.apple2Type = getApple2ComputerType();
myPreferences.setData(currentOptions); if (myGamepad)
myPreferences.setRegistry(getProperties()); {
currentOptions.joystick = myGamepad->name();
currentOptions.joystickId = myGamepad->deviceId();
}
myPreferences.setup(currentOptions, getProperties());
if (myPreferences.exec()) if (myPreferences.exec())
{ {
@ -347,6 +352,20 @@ void QApple::on_actionOptions_triggered()
HD_SetEnabled(newOptions.hdInSlot7); HD_SetEnabled(newOptions.hdInSlot7);
} }
if (newOptions.joystick.isEmpty())
{
myGamepad.reset();
Paddle::instance() = std::make_shared<Paddle>();
}
else
{
if (newOptions.joystickId != currentOptions.joystickId)
{
myGamepad.reset(new QGamepad(newOptions.joystickId));
Paddle::instance() = std::make_shared<GamepadPaddle>(myGamepad);
}
}
for (size_t i = 0; i < diskIDs.size(); ++i) for (size_t i = 0; i < diskIDs.size(); ++i)
{ {
if (currentOptions.disks[i] != newOptions.disks[i]) if (currentOptions.disks[i] != newOptions.disks[i])
@ -362,6 +381,7 @@ void QApple::on_actionOptions_triggered()
insertHD(newOptions.hds[i], hdIDs[i]); insertHD(newOptions.hds[i], hdIDs[i]);
} }
} }
} }
} }

View file

@ -4,6 +4,7 @@
#include "ui_qapple.h" #include "ui_qapple.h"
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QGamepad>
#include "preferences.h" #include "preferences.h"
class Emulator; class Emulator;
@ -52,6 +53,7 @@ private:
QElapsedTimer myElapsedTimer; QElapsedTimer myElapsedTimer;
QMdiSubWindow * myEmulatorWindow; QMdiSubWindow * myEmulatorWindow;
std::shared_ptr<QGamepad> myGamepad;
Emulator * myEmulator; Emulator * myEmulator;
int myMSGap; int myMSGap;

View file

@ -6,7 +6,7 @@
QT += core gui QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets gamepad
TARGET = qapple TARGET = qapple
TEMPLATE = app TEMPLATE = app
@ -22,7 +22,8 @@ SOURCES += main.cpp\
commands.cpp \ commands.cpp \
qhexedit.cpp \ qhexedit.cpp \
memorycontainer.cpp \ memorycontainer.cpp \
preferences.cpp preferences.cpp \
gamepadpaddle.cpp
HEADERS += qapple.h \ HEADERS += qapple.h \
emulator.h \ emulator.h \
@ -32,7 +33,8 @@ HEADERS += qapple.h \
commands.h \ commands.h \
qhexedit.h \ qhexedit.h \
memorycontainer.h \ memorycontainer.h \
preferences.h preferences.h \
gamepadpaddle.h
FORMS += qapple.ui \ FORMS += qapple.ui \
emulator.ui \ emulator.ui \

View file

@ -1,166 +0,0 @@
#include "StdAfx.h"
#include "linux/joy_input.h"
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <libevdev/libevdev.h>
#include "Log.h"
#include "Memory.h"
#include "Common.h"
#include "CPU.h"
unsigned __int64 g_nJoyCntrResetCycle = 0; // Abs cycle that joystick counters were reset
const double PDL_CNTR_INTERVAL = 2816.0 / 255.0; // 11.04 (From KEGS)
std::shared_ptr<Input> Input::ourSingleton;
void Input::initialise(const std::string & device)
{
ourSingleton.reset(new Input(device));
}
Input & Input::instance()
{
return *ourSingleton;
}
Input::Input(const std::string & device)
: myButtonCodes(2), myAxisCodes(2), myAxisMins(2), myAxisMaxs(2)
{
myFD = open(device.c_str(), O_RDONLY | O_NONBLOCK);
if (myFD > 0)
{
libevdev * dev;
int rc = libevdev_new_from_fd(myFD, &dev);
if (rc < 0)
{
LogFileOutput("Input: failed to init libevdev (%s): %s\n", strerror(-rc), device.c_str());
}
else
{
myDev.reset(dev, libevdev_free);
myButtonCodes[0] = BTN_SOUTH;
myButtonCodes[1] = BTN_EAST;
myAxisCodes[0] = ABS_X;
myAxisCodes[1] = ABS_Y;
for (size_t i = 0; i < myAxisCodes.size(); ++i)
{
myAxisMins[i] = libevdev_get_abs_minimum(myDev.get(), myAxisCodes[i]);
myAxisMaxs[i] = libevdev_get_abs_maximum(myDev.get(), myAxisCodes[i]);
}
}
}
else
{
LogFileOutput("Input: failed to open device (%s): %s\n", strerror(errno), device.c_str());
}
}
Input::~Input()
{
if (myFD > 0)
{
close(myFD);
}
}
int Input::poll()
{
int counter = 0;
if (!myDev)
{
return counter;
}
input_event ev;
int rc = LIBEVDEV_READ_STATUS_SUCCESS;
do
{
if (rc == LIBEVDEV_READ_STATUS_SYNC)
rc = libevdev_next_event(myDev.get(), LIBEVDEV_READ_FLAG_SYNC, &ev);
else
rc = libevdev_next_event(myDev.get(), LIBEVDEV_READ_FLAG_NORMAL, &ev);
++counter;
} while (rc >= 0);
return counter;
}
bool Input::getButton(int i) const
{
int value = 0;
if (myDev)
{
int rc = libevdev_fetch_event_value(myDev.get(), EV_KEY, myButtonCodes[i], &value);
}
return value != 0;
}
int Input::getAxis(int i) const
{
if (myDev)
{
int value = 0;
int rc = libevdev_fetch_event_value(myDev.get(), EV_ABS, myAxisCodes[i], &value);
int pdl = 255 * (value - myAxisMins[i]) / (myAxisMaxs[i] - myAxisMins[i]);
return pdl;
}
else
{
return 0;
}
}
BYTE __stdcall JoyReadButton(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
addr &= 0xFF;
BOOL pressed = 0;
switch (addr)
{
case 0x61:
pressed = Input::instance().getButton(0);
break;
case 0x62:
pressed = Input::instance().getButton(1);
break;
case 0x63:
break;
}
return MemReadFloatingBus(pressed, nCyclesLeft);
}
BYTE __stdcall JoyReadPosition(WORD pc, WORD address, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
const int nJoyNum = (address & 2) ? 1 : 0; // $C064..$C067
CpuCalcCycles(nCyclesLeft);
BOOL nPdlCntrActive = 0;
if (nJoyNum == 0)
{
int axis = address & 1;
int pdl = Input::instance().getAxis(axis);
// This is from KEGS. It helps games like Championship Lode Runner & Boulderdash
if (pdl >= 255)
pdl = 280;
nPdlCntrActive = g_nCumulativeCycles <= (g_nJoyCntrResetCycle + (unsigned __int64) ((double)pdl * PDL_CNTR_INTERVAL));
}
return MemReadFloatingBus(nPdlCntrActive, nCyclesLeft);
}
BYTE __stdcall JoyResetPosition(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
CpuCalcCycles(nCyclesLeft);
g_nJoyCntrResetCycle = g_nCumulativeCycles;
return MemReadFloatingBus(nCyclesLeft);
}

94
source/linux/paddle.cpp Normal file
View file

@ -0,0 +1,94 @@
#include "StdAfx.h"
#include "linux/paddle.h"
#include "Log.h"
#include "Memory.h"
#include "Common.h"
#include "CPU.h"
unsigned __int64 g_nJoyCntrResetCycle = 0; // Abs cycle that joystick counters were reset
const double PDL_CNTR_INTERVAL = 2816.0 / 255.0; // 11.04 (From KEGS)
Paddle::Paddle()
{
}
bool Paddle::getButton(int i) const
{
return false;
}
int Paddle::getAxis(int i) const
{
return 0;
}
std::shared_ptr<const Paddle> & Paddle::instance()
{
static std::shared_ptr<const Paddle> singleton = std::make_shared<Paddle>();
return singleton;
}
Paddle::~Paddle()
{
}
BYTE __stdcall JoyReadButton(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
addr &= 0xFF;
BOOL pressed = 0;
const std::shared_ptr<const Paddle> & paddle = Paddle::instance();
if (paddle)
{
switch (addr)
{
case 0x61:
pressed = paddle->getButton(0);
break;
case 0x62:
pressed = paddle->getButton(1);
break;
case 0x63:
break;
}
}
return MemReadFloatingBus(pressed, nCyclesLeft);
}
BYTE __stdcall JoyReadPosition(WORD pc, WORD address, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
const int nJoyNum = (address & 2) ? 1 : 0; // $C064..$C067
CpuCalcCycles(nCyclesLeft);
BOOL nPdlCntrActive = 0;
const std::shared_ptr<const Paddle> & paddle = Paddle::instance();
if (paddle)
{
if (nJoyNum == 0)
{
int axis = address & 1;
int pdl = paddle->getAxis(axis);
// This is from KEGS. It helps games like Championship Lode Runner & Boulderdash
if (pdl >= 255)
pdl = 280;
nPdlCntrActive = g_nCumulativeCycles <= (g_nJoyCntrResetCycle + (unsigned __int64) ((double)pdl * PDL_CNTR_INTERVAL));
}
}
return MemReadFloatingBus(nPdlCntrActive, nCyclesLeft);
}
BYTE __stdcall JoyResetPosition(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
CpuCalcCycles(nCyclesLeft);
g_nJoyCntrResetCycle = g_nCumulativeCycles;
return MemReadFloatingBus(nCyclesLeft);
}

16
source/linux/paddle.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include <memory>
class Paddle
{
public:
Paddle();
virtual ~Paddle();
virtual bool getButton(int i) const;
virtual int getAxis(int i) const;
static std::shared_ptr<const Paddle> & instance();
};