From b71630379a04b046d8f764a5327886a1459ca676 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Fri, 24 Jul 2020 16:56:48 -0400 Subject: [PATCH] Added game pad button mapping profile logic. --- src/driver.h | 1 + src/drivers/Qt/GamePadConf.cpp | 202 +++++++++++++++- src/drivers/Qt/GamePadConf.h | 6 + src/drivers/Qt/config.cpp | 6 +- src/drivers/Qt/sdl-joystick.cpp | 401 +++++++++++++++++++++++++++----- src/drivers/Qt/sdl-joystick.h | 17 +- src/file.cpp | 5 + 7 files changed, 559 insertions(+), 79 deletions(-) diff --git a/src/driver.h b/src/driver.h index 9987643c..7838369f 100644 --- a/src/driver.h +++ b/src/driver.h @@ -142,6 +142,7 @@ void FCEUI_SetRenderedLines(int ntscf, int ntscl, int palf, int pall); //Sets the base directory(save states, snapshots, etc. are saved in directories below this directory. void FCEUI_SetBaseDirectory(std::string const & dir); +const char *FCEUI_GetBaseDirectory(void); bool FCEUI_GetUserPaletteAvail(void); void FCEUI_SetUserPalette(uint8 *pal, int nEntries); diff --git a/src/drivers/Qt/GamePadConf.cpp b/src/drivers/Qt/GamePadConf.cpp index 8f21ce17..e390e485 100644 --- a/src/drivers/Qt/GamePadConf.cpp +++ b/src/drivers/Qt/GamePadConf.cpp @@ -1,5 +1,8 @@ // GamePadConf.cpp // +#include +#include + #include "Qt/GamePadConf.h" #include "Qt/main.h" #include "Qt/dface.h" @@ -13,13 +16,16 @@ GamePadConfDialog_t::GamePadConfDialog_t(QWidget *parent) : QDialog( parent ) { - QHBoxLayout *hbox1, *hbox2, *hbox3, *hbox4; + QHBoxLayout *hbox, *hbox1, *hbox2, *hbox3, *hbox4; + QVBoxLayout *vbox; QGridLayout *grid; QCheckBox *efs_chkbox, *udlr_chkbox; QGroupBox *frame1, *frame2; QLabel *label; QPushButton *newProfileButton; + QPushButton *saveProfileButton; QPushButton *applyProfileButton; + QPushButton *removeProfileButton; QPushButton *loadDefaultButton; QPushButton *clearAllButton; QPushButton *closebutton; @@ -64,7 +70,7 @@ GamePadConfDialog_t::GamePadConfDialog_t(QWidget *parent) if ( js != NULL ) { - if ( js->inUse() ) + if ( js->isConnected() ) { char stmp[128]; sprintf( stmp, "%i: %s", i, js->getName() ); @@ -80,19 +86,45 @@ GamePadConfDialog_t::GamePadConfDialog_t(QWidget *parent) hbox3->addWidget( guidLbl ); frame1 = new QGroupBox(tr("Mapping Profile:")); - grid = new QGridLayout(); + //grid = new QGridLayout(); + vbox = new QVBoxLayout(); - frame1->setLayout( grid ); + //frame1->setLayout( grid ); + frame1->setLayout( vbox ); + + hbox = new QHBoxLayout(); + vbox->addLayout( hbox ); mapSel = new QComboBox(); + hbox->addWidget( mapSel ); + + mapSel->setWhatsThis( tr("Combo box for selection of a saved button mapping profile for the selected device")); + mapSel->addItem( tr("default"), 0 ); + + hbox = new QHBoxLayout(); + vbox->addLayout( hbox ); + applyProfileButton = new QPushButton( tr("Load") ); + applyProfileButton->setWhatsThis(tr("Sets Current Active Map to the Selected Profile")); + hbox->addWidget( applyProfileButton ); + + saveProfileButton = new QPushButton( tr("Save") ); + saveProfileButton->setWhatsThis(tr("Stores Current Active Map to the Selected Profile")); + hbox->addWidget( saveProfileButton ); + + hbox = new QHBoxLayout(); + vbox->addLayout( hbox ); + newProfileButton = new QPushButton( tr("New") ); + newProfileButton->setWhatsThis(tr("Create a New Map Profile")); + hbox->addWidget( newProfileButton ); - grid->addWidget( mapSel , 0, 0, Qt::AlignCenter ); - grid->addWidget( applyProfileButton, 0, 1, Qt::AlignCenter ); - grid->addWidget( newProfileButton , 0, 2, Qt::AlignCenter ); + removeProfileButton = new QPushButton( tr("Delete") ); + removeProfileButton->setWhatsThis(tr("Deletes the Selected Map Profile")); + hbox->addWidget( removeProfileButton ); - mapSel->addItem( tr("Default"), 0 ); + mapMsg = new QLabel(); + vbox->addWidget(mapMsg); efs_chkbox = new QCheckBox( tr("Enable Four Score") ); udlr_chkbox = new QCheckBox( tr("Allow Up+Down/Left+Right") ); @@ -169,6 +201,10 @@ GamePadConfDialog_t::GamePadConfDialog_t(QWidget *parent) connect(clearButton[8], SIGNAL(clicked()), this, SLOT(clearButton8(void)) ); connect(clearButton[9], SIGNAL(clicked()), this, SLOT(clearButton9(void)) ); + connect(newProfileButton , SIGNAL(clicked()), this, SLOT(newProfileCallback(void)) ); + connect(applyProfileButton, SIGNAL(clicked()), this, SLOT(loadProfileCallback(void)) ); + connect(saveProfileButton , SIGNAL(clicked()), this, SLOT(saveProfileCallback(void)) ); + connect(loadDefaultButton, SIGNAL(clicked()), this, SLOT(loadDefaults(void)) ); connect(clearAllButton , SIGNAL(clicked()), this, SLOT(clearAllCallback(void)) ); connect(closebutton , SIGNAL(clicked()), this, SLOT(closeWindow(void)) ); @@ -192,6 +228,8 @@ GamePadConfDialog_t::GamePadConfDialog_t(QWidget *parent) setLayout( mainLayout ); inputTimer->start( 33 ); // 30hz + + loadMapList(); } //---------------------------------------------------- @@ -212,6 +250,62 @@ void GamePadConfDialog_t::keyReleaseEvent(QKeyEvent *event) pushKeyEvent( event, 0 ); } //---------------------------------------------------- +void GamePadConfDialog_t::loadMapList(void) +{ + QDir dir; + QStringList filters, fileList; + const char *baseDir = FCEUI_GetBaseDirectory(); + const char *guid; + std::string path; + int index, devIdx; + jsDev_t *js; + + index = devSel->currentIndex(); + devIdx = devSel->itemData(index).toInt(); + + if ( devIdx < 0 ) + { + guid = "keyboard"; + } + else + { + js = getJoystickDevice( devIdx ); + + guid = js->getGUID(); + } + + if ( guid == NULL ) + { + return; + } + + path = std::string(baseDir) + "/input/" + std::string(guid); + + dir.setPath( QString::fromStdString(path) ); + + filters << "*.txt"; + dir.setNameFilters(filters); + + fileList = dir.entryList( filters, QDir::Files, QDir::NoSort ); + + mapSel->clear(); + mapSel->addItem( tr("default"), 0 ); + + for (size_t i=0; i < fileList.size(); i++) + { + size_t suffixIdx; + std::string fileName = fileList[i].toStdString(); + + suffixIdx = fileName.find_last_of('.'); + + fileName.erase( suffixIdx ); + + //printf("File: %s \n", fileName.c_str() ); + + mapSel->addItem( tr(fileName.c_str()), (int)i+1 ); + } +} +//---------------------------------------------------- void GamePadConfDialog_t::updateCntrlrDpy(void) { char keyNameStr[128]; @@ -247,7 +341,7 @@ void GamePadConfDialog_t::deviceSelect(int index) if ( js != NULL ) { - if ( js->inUse() ) + if ( js->isConnected() ) { guidLbl->setText( js->getGUID() ); } @@ -256,6 +350,9 @@ void GamePadConfDialog_t::deviceSelect(int index) { guidLbl->setText(""); } + GamePad[portNum].setDeviceIndex( devIdx ); + + loadMapList(); updateCntrlrDpy(); } @@ -472,7 +569,7 @@ void GamePadConfDialog_t::loadDefaults(void) index = devSel->currentIndex(); devIdx = devSel->itemData(index).toInt(); - printf("Selected Device:%i : %i \n", index, devIdx ); + //printf("Selected Device:%i : %i \n", index, devIdx ); if ( devIdx == -1 ) { @@ -506,12 +603,95 @@ void GamePadConfDialog_t::loadDefaults(void) } else { - GamePad[portNum].devIdx = devIdx; + GamePad[portNum].setDeviceIndex( devIdx ); GamePad[portNum].loadDefaults(); } updateCntrlrDpy(); } //---------------------------------------------------- +void GamePadConfDialog_t::createNewProfile( const char *name ) +{ + printf("Creating: %s \n", name ); + + GamePad[portNum].createProfile(name); + + mapSel->addItem( tr(name) ); +} +//---------------------------------------------------- +void GamePadConfDialog_t::newProfileCallback(void) +{ + int ret; + QInputDialog dialog(this); + + dialog.setWindowTitle( tr("New Profile") ); + dialog.setLabelText( tr("Specify New Profile Name") ); + dialog.setOkButtonText( tr("Create") ); + + dialog.show(); + ret = dialog.exec(); + + if ( QDialog::Accepted == ret ) + { + createNewProfile( dialog.textValue().toStdString().c_str() ); + } +} +//---------------------------------------------------- +void GamePadConfDialog_t::loadProfileCallback(void) +{ + char stmp[256]; + int index, devIdx, ret; + std::string mapName; + + index = devSel->currentIndex(); + devIdx = devSel->itemData(index).toInt(); + + mapName = mapSel->currentText().toStdString(); + + GamePad[portNum].setDeviceIndex( devIdx ); + + if ( mapName.compare("default") == 0 ) + { + ret =GamePad[portNum].loadDefaults(); + } + else + { + ret = GamePad[portNum].loadProfile( mapName.c_str() ); + } + if ( ret == 0 ) + { + sprintf( stmp, "Mapping Loaded: %s/%s \n", GamePad[portNum].getGUID(), mapName.c_str() ); + } + else + { + sprintf( stmp, "Error: Failed to Load Mapping: %s/%s \n", GamePad[portNum].getGUID(), mapName.c_str() ); + } + mapMsg->setText( tr(stmp) ); + + updateCntrlrDpy(); +} +//---------------------------------------------------- +void GamePadConfDialog_t::saveProfileCallback(void) +{ + int ret; + std::string mapName; + char stmp[256]; + + mapName = mapSel->currentText().toStdString(); + + ret = GamePad[portNum].saveCurrentMapToFile( mapName.c_str() ); + + if ( ret == 0 ) + { + sprintf( stmp, "Mapping Saved: %s/%s \n", GamePad[portNum].getGUID(), mapName.c_str() ); + } + else + { + sprintf( stmp, "Error: Failed to Save Mapping: %s \n", mapName.c_str() ); + } + mapMsg->setText( tr(stmp) ); + +} +//---------------------------------------------------- void GamePadConfDialog_t::updatePeriodic(void) { for (int i=0; i #include "Qt/sdl.h" #include "Qt/sdl-joystick.h" @@ -41,6 +42,13 @@ static int sdlButton2NesGpIdx( const char *id ); // Static Variables static int s_jinited = 0; +static const char *buttonNames[ GAMEPAD_NUM_BUTTONS ] = +{ + "a", "b","back","start", + "dpup","dpdown","dpleft","dpright", + "turboA","turboB" +}; + //******************************************************************************** // Joystick Device jsDev_t::jsDev_t(void) @@ -91,7 +99,7 @@ bool jsDev_t::isGameController(void) } //******************************************************************************** -bool jsDev_t::inUse(void) +bool jsDev_t::isConnected(void) { return ( (js != NULL) || (gc != NULL) ); } @@ -151,15 +159,23 @@ static jsDev_t jsDev[ MAX_JOYSTICKS ]; //******************************************************************************** nesGamePadMap_t::nesGamePadMap_t(void) { - memset( guid, 0, sizeof(guid) ); - memset( name, 0, sizeof(name) ); - memset( os , 0, sizeof(os) ); - memset( btn , 0, sizeof(btn) ); + clearMapping(); } //******************************************************************************** nesGamePadMap_t::~nesGamePadMap_t(void) { +} +//******************************************************************************** +void nesGamePadMap_t::clearMapping(void) +{ + guid[0] = 0; + name[0] = 0; + os[0] = 0; + for (int i=0; ibtn[i][0] == 'k') { + SDL_Keycode key; + bmap[i].ButtType = BUTTC_KEYBOARD; - bmap[i].DeviceNum = 0; - bmap[i].ButtonNum = 0; // FIXME + bmap[i].DeviceNum = -1; + + key = SDL_GetKeyFromName( &gpm->btn[i][1] ); + + if ( key != SDLK_UNKNOWN ) + { + bmap[i].ButtonNum = key; + } + else + { + bmap[i].ButtonNum = -1; + } } else if ( (gpm->btn[i][0] == 'b') && isdigit( gpm->btn[i][1] ) ) { @@ -369,24 +386,280 @@ int GamePad_t::setMapping( const char *map ) return 0; } //******************************************************************************** +int GamePad_t::getMapFromFile( const char *filename, char *out ) +{ + int i=0,j=0; + FILE *fp; + char line[256]; + + out[0] = 0; + + fp = ::fopen( filename, "r" ); + + if ( fp == NULL ) + { + return -1; + } + while ( fgets( line, sizeof(line), fp ) != 0 ) + { + i=0; + while (line[i] != 0) + { + if ( line[i] == '#' ) + { + line[i] = 0; break; + } + i++; + } + + if ( i < 32 ) continue; // need at least 32 chars for a valid line entry + + i=0; j=0; + while ( isspace(line[i]) ) i++; + + while ( line[i] != 0 ) + { + out[j] = line[i]; i++; j++; + } + out[j] = 0; + + if ( j < 34 ) continue; + + break; + } + + ::fclose(fp); + + return (j < 34); +} +//******************************************************************************** +int GamePad_t::getDefaultMap( char *out, const char *guid ) +{ + char txtMap[256]; + const char *baseDir = FCEUI_GetBaseDirectory(); + std::string path; + + out[0] = 0; + + if ( devIdx < 0 ) + { + guid = "keyboard"; + } + if ( guid == NULL ) + { + if ( jsDev[ devIdx ].isConnected() ) + { + guid = jsDev[ devIdx ].getGUID(); + } + } + if ( guid == NULL ) + { + return -1; + } + + path = std::string(baseDir) + "/input/" + std::string(guid) + "/default.txt"; + + if ( getMapFromFile( path.c_str(), txtMap ) == 0 ) + { + printf("Using Mapping From File: %s\n", path.c_str() ); + strcpy( out, txtMap ); + return 0; + } + + if ( devIdx >= 0 ) + { + if ( jsDev[ devIdx ].gc ) + { + char *sdlMapping; + + sdlMapping = SDL_GameControllerMapping( jsDev[ devIdx ].gc ); + + if ( sdlMapping == NULL ) return -1; + + strcpy( out, sdlMapping ); + + SDL_free(sdlMapping); + + return 0; + } + } + return -1; +} +//******************************************************************************** int GamePad_t::loadDefaults(void) { + char txtMap[256]; - if ( jsDev[ devIdx ].gc ) - { - char *mapping; + if ( getDefaultMap( txtMap ) == 0 ) + { + setMapping( txtMap ); + } - mapping = SDL_GameControllerMapping( jsDev[ devIdx ].gc ); - - if ( mapping == NULL ) return -1; - - setMapping( mapping ); - - SDL_free(mapping); - } return 0; } //******************************************************************************** +int GamePad_t::loadProfile( const char *name, const char *guid ) +{ + char txtMap[256]; + const char *baseDir = FCEUI_GetBaseDirectory(); + std::string path; + + if ( devIdx < 0 ) + { + guid = "keyboard"; + } + if ( guid == NULL ) + { + if ( jsDev[ devIdx ].isConnected() ) + { + guid = jsDev[ devIdx ].getGUID(); + } + } + if ( guid == NULL ) + { + return -1; + } + + path = std::string(baseDir) + "/input/" + std::string(guid) + + "/" + std::string(name) + ".txt"; + + //printf("Using File: %s\n", path.c_str() ); + + if ( getMapFromFile( path.c_str(), txtMap ) == 0 ) + { + setMapping( txtMap ); + return 0; + } + + return -1; +} +//******************************************************************************** +int GamePad_t::saveCurrentMapToFile( const char *name ) +{ + int i; + char stmp[64]; + const char *guid = NULL; + const char *baseDir = FCEUI_GetBaseDirectory(); + std::string path, output; + QDir dir; + + if ( devIdx >= 0 ) + { + if ( !jsDev[devIdx].isConnected() ) + { + printf("Error: JS%i Not Connected\n", devIdx ); + return -1; + } + guid = jsDev[devIdx].getGUID(); + } + else + { + guid = "keyboard"; + } + path = std::string(baseDir) + "/input/" + std::string(guid); + + dir.mkpath( QString::fromStdString(path) ); + + path += "/" + std::string(name) + ".txt"; + + output.assign( guid ); + output.append( "," ); + output.append( name ); + output.append( "," ); + + for (i=0; i> 8) & 0x1F, bmap[i].ButtonNum & 0xFF ); + } + else if (bmap[i].ButtonNum & 0x8000) + { + /* Axis "button" */ + sprintf( stmp, "%ca%i", + (bmap[i].ButtonNum & 0x4000) ? '-' : '+', bmap[i].ButtonNum & 0x3FFF ); + } + else + { + /* Button */ + sprintf( stmp, "b%i", bmap[i].ButtonNum ); + } + } + output.append( buttonNames[i] ); + output.append( ":" ); + output.append( stmp ); + output.append( "," ); + } + + return saveMappingToFile( path.c_str(), output.c_str() ); +} +//******************************************************************************** +int GamePad_t::saveMappingToFile( const char *filename, const char *txtMap ) +{ + FILE *fp; + + fp = ::fopen(filename, "w"); + + if ( fp == NULL ) + { + return -1; + } + fprintf( fp, "%s\n", txtMap ); + + ::fclose(fp); + + return 0; +} +//******************************************************************************** +int GamePad_t::createProfile( const char *name ) +{ + char txtMap[256]; + const char *guid = NULL; + const char *baseDir = FCEUI_GetBaseDirectory(); + std::string path; + QDir dir; + + if ( baseDir[0] == 0 ) + { + printf("Error: Invalid base directory\n"); + return -1; + } + if ( devIdx >= 0 ) + { + if ( !jsDev[devIdx].isConnected() ) + { + printf("Error: JS%i Not Connected\n", devIdx ); + return -1; + } + guid = jsDev[devIdx].getGUID(); + } + else + { + guid = "keyboard"; + } + path = std::string(baseDir) + "/input/" + std::string(guid); + + dir.mkpath( QString::fromStdString(path) ); + //printf("DIR: '%s'\n", path.c_str() ); + + path += "/" + std::string(name) + ".txt"; + + //printf("File: '%s'\n", path.c_str() ); + + getDefaultMap( txtMap, guid ); + + saveMappingToFile( path.c_str(), txtMap ); + + return 0; +} //******************************************************************************** jsDev_t *getJoystickDevice( int devNum ) { @@ -410,6 +683,10 @@ DTestButtonJoy(ButtConfig *bc) { return 0; } + if ( bc->DeviceNum < 0 ) + { + return 0; + } js = jsDev[bc->DeviceNum].getJS(); if (bc->ButtonNum & 0x2000) @@ -432,7 +709,7 @@ DTestButtonJoy(ButtConfig *bc) /* Axis "button" */ int pos; pos = SDL_JoystickGetAxis( js, - bc->ButtonNum & 16383); + bc->ButtonNum & 0x3FFF); if ((bc->ButtonNum & 0x4000) && pos <= -16383) { bc->state = 1; @@ -508,7 +785,7 @@ KillJoysticks(void) //******************************************************************************** int AddJoystick( int which ) { - if ( jsDev[ which ].inUse() ) + if ( jsDev[ which ].isConnected() ) { //printf("Error: Joystick already exists at device index %i \n", which ); return -1; @@ -560,7 +837,7 @@ int RemoveJoystick( int which ) for (int i=0; i