From 348ba1ecd91af24c6bbda689cfd8c8669c5046dc Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Fri, 6 Oct 2017 20:48:14 +0100 Subject: [PATCH] Use QGamepad in QApple. This requires an interface as napple uses libevdev. Signed-off-by: Andrea Odetti --- CMakeLists.txt | 3 +- source/frontends/ncurses/evdevpaddle.cpp | 107 +++++++++++ .../ncurses/evdevpaddle.h} | 22 ++- source/frontends/ncurses/world.cpp | 11 +- source/frontends/qapple/gamepadpaddle.cpp | 38 ++++ source/frontends/qapple/gamepadpaddle.h | 20 +++ source/frontends/qapple/preferences.cpp | 36 ++++ source/frontends/qapple/preferences.h | 10 +- source/frontends/qapple/preferences.ui | 10 ++ source/frontends/qapple/qapple.cpp | 30 +++- source/frontends/qapple/qapple.h | 2 + source/frontends/qapple/qapple.pro | 8 +- source/linux/joy_input.cpp | 166 ------------------ source/linux/paddle.cpp | 94 ++++++++++ source/linux/paddle.h | 16 ++ 15 files changed, 381 insertions(+), 192 deletions(-) create mode 100644 source/frontends/ncurses/evdevpaddle.cpp rename source/{linux/joy_input.h => frontends/ncurses/evdevpaddle.h} (57%) create mode 100644 source/frontends/qapple/gamepadpaddle.cpp create mode 100644 source/frontends/qapple/gamepadpaddle.h delete mode 100644 source/linux/joy_input.cpp create mode 100644 source/linux/paddle.cpp create mode 100644 source/linux/paddle.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b7aeb34..1fd4a5c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ add_library(appleii SHARED source/linux/wwrapper.cpp source/linux/state.cpp source/linux/benchmark.cpp - source/linux/joy_input.cpp + source/linux/paddle.cpp source/Z80VICE/z80.cpp source/Z80VICE/z80mem.cpp @@ -44,6 +44,7 @@ add_executable(applen source/frontends/ncurses/main.cpp source/frontends/ncurses/world.cpp source/frontends/ncurses/colors.cpp + source/frontends/ncurses/evdevpaddle.cpp source/frontends/ncurses/nframe.cpp source/frontends/ncurses/asciiart.cpp source/frontends/ncurses/resources.cpp diff --git a/source/frontends/ncurses/evdevpaddle.cpp b/source/frontends/ncurses/evdevpaddle.cpp new file mode 100644 index 00000000..5d4850ca --- /dev/null +++ b/source/frontends/ncurses/evdevpaddle.cpp @@ -0,0 +1,107 @@ +#include "StdAfx.h" +#include "frontends/ncurses/evdevpaddle.h" + +#include +#include +#include + +#include + +#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; + } + +} diff --git a/source/linux/joy_input.h b/source/frontends/ncurses/evdevpaddle.h similarity index 57% rename from source/linux/joy_input.h rename to source/frontends/ncurses/evdevpaddle.h index 8dea342a..25e4aea4 100644 --- a/source/linux/joy_input.h +++ b/source/frontends/ncurses/evdevpaddle.h @@ -1,26 +1,24 @@ #pragma once +#include "linux/paddle.h" + #include -#include #include struct libevdev; struct input_event; -class Input +class EvDevPaddle : public Paddle { public: - Input(const std::string & device); - ~Input(); + EvDevPaddle(const std::string & device); + ~EvDevPaddle(); int poll(); - bool getButton(int i) const; - int getAxis(int i) const; - - static void initialise(const std::string & device); - - static Input & instance(); + const std::string & getName() const; + virtual bool getButton(int i) const; + virtual int getAxis(int i) const; private: int myFD; @@ -28,10 +26,10 @@ private: void process(const input_event & ev); + std::string myName; + std::vector myButtonCodes; std::vector myAxisCodes; std::vector myAxisMins; std::vector myAxisMaxs; - - static std::shared_ptr ourSingleton; }; diff --git a/source/frontends/ncurses/world.cpp b/source/frontends/ncurses/world.cpp index 6ac90c35..6aa8f58d 100644 --- a/source/frontends/ncurses/world.cpp +++ b/source/frontends/ncurses/world.cpp @@ -13,11 +13,12 @@ #include "Memory.h" #include "linux/interface.h" -#include "linux/joy_input.h" +#include "linux/paddle.h" #include "frontends/ncurses/nframe.h" #include "frontends/ncurses/colors.h" #include "frontends/ncurses/asciiart.h" +#include "frontends/ncurses/evdevpaddle.h" namespace { @@ -25,6 +26,7 @@ namespace std::shared_ptr frame; std::shared_ptr colors; std::shared_ptr asciiArt; + std::shared_ptr paddle; int g_nTrackDrive1 = -1; int g_nTrackDrive2 = -1; @@ -372,7 +374,10 @@ void VideoInitialize() frame.reset(new Frame()); 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); } @@ -515,7 +520,7 @@ int ProcessKeyboard() void ProcessInput() { - Input::instance().poll(); + paddle->poll(); } BYTE KeybGetKeycode () diff --git a/source/frontends/qapple/gamepadpaddle.cpp b/source/frontends/qapple/gamepadpaddle.cpp new file mode 100644 index 00000000..42bd5f21 --- /dev/null +++ b/source/frontends/qapple/gamepadpaddle.cpp @@ -0,0 +1,38 @@ +#include "gamepadpaddle.h" + +GamepadPaddle::GamepadPaddle(const std::shared_ptr & 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; +} diff --git a/source/frontends/qapple/gamepadpaddle.h b/source/frontends/qapple/gamepadpaddle.h new file mode 100644 index 00000000..91e4c999 --- /dev/null +++ b/source/frontends/qapple/gamepadpaddle.h @@ -0,0 +1,20 @@ +#ifndef GAMEPADPADDLE_H +#define GAMEPADPADDLE_H + +#include + +#include "linux/paddle.h" + +class GamepadPaddle : public Paddle +{ +public: + GamepadPaddle(const std::shared_ptr & gamepad); + + virtual bool getButton(int i) const; + virtual int getAxis(int i) const; + +private: + const std::shared_ptr myGamepad; +}; + +#endif // GAMEPADPADDLE_H diff --git a/source/frontends/qapple/preferences.cpp b/source/frontends/qapple/preferences.cpp index 44501442..e3d6f23a 100644 --- a/source/frontends/qapple/preferences.cpp +++ b/source/frontends/qapple/preferences.cpp @@ -1,5 +1,6 @@ #include "preferences.h" #include +#include namespace { @@ -105,6 +106,32 @@ Preferences::Preferences(QWidget *parent) : 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 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) { registryTree->clear(); @@ -126,6 +153,8 @@ void Preferences::setData(const Data & data) // synchronise on_hd_7_clicked(data.hdInSlot7); + + joystick->setCurrentText(data.joystick); } Preferences::Data Preferences::getData() const @@ -139,6 +168,13 @@ Preferences::Data Preferences::getData() const data.mouseInSlot4 = mouse_4->isChecked(); data.cpmInSlot5 = cpm_5->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; } diff --git a/source/frontends/qapple/preferences.h b/source/frontends/qapple/preferences.h index be775f5c..2fc6c9b4 100644 --- a/source/frontends/qapple/preferences.h +++ b/source/frontends/qapple/preferences.h @@ -18,14 +18,17 @@ public: bool mouseInSlot4; bool cpmInSlot5; bool hdInSlot7; + + QString joystick; + int joystickId; // only putput + std::vector disks; std::vector hds; }; explicit Preferences(QWidget *parent); - void setRegistry(const boost::property_tree::ptree & registry); - void setData(const Data & data); + void setup(const Data & data, const boost::property_tree::ptree & registry); Data getData() const; private slots: @@ -45,6 +48,9 @@ private: std::vector myDisks; std::vector myHDs; + void setRegistry(const boost::property_tree::ptree & registry); + void setData(const Data & data); + void populateJoysticks(); void browseDisk(const std::vector & disks, const size_t id); }; diff --git a/source/frontends/qapple/preferences.ui b/source/frontends/qapple/preferences.ui index 1de4e023..0a4f9485 100644 --- a/source/frontends/qapple/preferences.ui +++ b/source/frontends/qapple/preferences.ui @@ -145,6 +145,16 @@ + + + + Joystick + + + + + + diff --git a/source/frontends/qapple/qapple.cpp b/source/frontends/qapple/qapple.cpp index dc4ce881..402f8336 100644 --- a/source/frontends/qapple/qapple.cpp +++ b/source/frontends/qapple/qapple.cpp @@ -17,10 +17,11 @@ #include "linux/data.h" #include "linux/configuration.h" #include "linux/benchmark.h" -#include "linux/joy_input.h" +#include "linux/paddle.h" #include "emulator.h" #include "memorycontainer.h" +#include "gamepadpaddle.h" #include #include @@ -34,7 +35,6 @@ namespace setbuf(g_fh, NULL); InitializeRegistry("../qapple/applen.conf"); - Input::initialise("/dev/input/by-id/usb-©Microsoft_Corporation_Controller_1BBE3DB-event-joystick"); LogFileOutput("Initialisation\n"); @@ -206,7 +206,7 @@ void QApple::on_timer() g_dwCyclesThisFrame -= dwClksPerFrame; myEmulator->redrawScreen(); } - Input::instance().poll(); + //Input::instance().poll(); } while (DiskIsSpinning()); } @@ -314,8 +314,13 @@ void QApple::on_actionOptions_triggered() currentOptions.apple2Type = getApple2ComputerType(); - myPreferences.setData(currentOptions); - myPreferences.setRegistry(getProperties()); + if (myGamepad) + { + currentOptions.joystick = myGamepad->name(); + currentOptions.joystickId = myGamepad->deviceId(); + } + + myPreferences.setup(currentOptions, getProperties()); if (myPreferences.exec()) { @@ -347,6 +352,20 @@ void QApple::on_actionOptions_triggered() HD_SetEnabled(newOptions.hdInSlot7); } + if (newOptions.joystick.isEmpty()) + { + myGamepad.reset(); + Paddle::instance() = std::make_shared(); + } + else + { + if (newOptions.joystickId != currentOptions.joystickId) + { + myGamepad.reset(new QGamepad(newOptions.joystickId)); + Paddle::instance() = std::make_shared(myGamepad); + } + } + for (size_t i = 0; i < diskIDs.size(); ++i) { if (currentOptions.disks[i] != newOptions.disks[i]) @@ -362,6 +381,7 @@ void QApple::on_actionOptions_triggered() insertHD(newOptions.hds[i], hdIDs[i]); } } + } } diff --git a/source/frontends/qapple/qapple.h b/source/frontends/qapple/qapple.h index 27e52632..40115dc5 100644 --- a/source/frontends/qapple/qapple.h +++ b/source/frontends/qapple/qapple.h @@ -4,6 +4,7 @@ #include "ui_qapple.h" #include +#include #include "preferences.h" class Emulator; @@ -52,6 +53,7 @@ private: QElapsedTimer myElapsedTimer; QMdiSubWindow * myEmulatorWindow; + std::shared_ptr myGamepad; Emulator * myEmulator; int myMSGap; diff --git a/source/frontends/qapple/qapple.pro b/source/frontends/qapple/qapple.pro index 7a64edb7..4205c338 100644 --- a/source/frontends/qapple/qapple.pro +++ b/source/frontends/qapple/qapple.pro @@ -6,7 +6,7 @@ QT += core gui -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets gamepad TARGET = qapple TEMPLATE = app @@ -22,7 +22,8 @@ SOURCES += main.cpp\ commands.cpp \ qhexedit.cpp \ memorycontainer.cpp \ - preferences.cpp + preferences.cpp \ + gamepadpaddle.cpp HEADERS += qapple.h \ emulator.h \ @@ -32,7 +33,8 @@ HEADERS += qapple.h \ commands.h \ qhexedit.h \ memorycontainer.h \ - preferences.h + preferences.h \ + gamepadpaddle.h FORMS += qapple.ui \ emulator.ui \ diff --git a/source/linux/joy_input.cpp b/source/linux/joy_input.cpp deleted file mode 100644 index e34291cd..00000000 --- a/source/linux/joy_input.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include "StdAfx.h" - -#include "linux/joy_input.h" - -#include -#include -#include - -#include - -#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::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); -} diff --git a/source/linux/paddle.cpp b/source/linux/paddle.cpp new file mode 100644 index 00000000..55080998 --- /dev/null +++ b/source/linux/paddle.cpp @@ -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 & Paddle::instance() +{ + static std::shared_ptr singleton = std::make_shared(); + 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 & 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 & 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); +} diff --git a/source/linux/paddle.h b/source/linux/paddle.h new file mode 100644 index 00000000..2dc4fb5f --- /dev/null +++ b/source/linux/paddle.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class Paddle +{ +public: + Paddle(); + + virtual ~Paddle(); + + virtual bool getButton(int i) const; + virtual int getAxis(int i) const; + + static std::shared_ptr & instance(); +};