// JLH = James Hammons <jlhamm@acm.org>
//
// Who When What
-// --- ---------- -------------------------------------------------------------
+// --- ---------- ------------------------------------------------------------
// JLH 05/01/2013 Created this file
+// JLH 10/02/2014 Finally fixed stuff so it works the way it should
+//
+// This is a profile database with two parts: One, a list of devices, and two,
+// a list of profiles each containing a pointer to the device list, and map
+// name, a preferred slot #, and a key/button map. All the heavy lifting (incl.
+// autoconnection of devices to profiles to slots) is done here.
+//
+// Basically, how this works is that we connect the device the user plugs into
+// the computer to a profile in the database to a slot in the virtual Jaguar.
+// Hopefully the configuration that the user gives us is sane enough for us to
+// figure out how to do the right thing! By default, there is always a keyboard
+// device plugged in; any other device that gets plugged in and wants to be in
+// slot #0 can override it. This is so there is always a sane configuration if
+// nothing is plugged in.
+//
+// Devices go into the database when the user plugs them in and runs VJ, and
+// subsequently does anything to alter any of the existing profiles. Once a
+// device has been seen, it can't be unseen!
//
-
#include "profile.h"
#include <QtGui>
#include "gamepad.h"
+#include "log.h"
#include "settings.h"
+//#define DEBUG_PROFILES
#define MAX_DEVICES 64
Profile profile[MAX_PROFILES];
+Profile profileBackup[MAX_PROFILES];
int controller1Profile;
int controller2Profile;
-int gamepad1Slot;
-int gamepad2Slot;
+int gamepadIDSlot1;
+int gamepadIDSlot2;
int numberOfProfiles;
int numberOfDevices;
char deviceNames[MAX_DEVICES][128];
+static int numberOfProfilesSave;
// This is so that new devices have something reasonable to show for default
uint32_t defaultMap[21] = {
};
+// Function Prototypes
+int ConnectProfileToDevice(int deviceNum, int gamepadID = -1);
+int FindProfileForDevice(int deviceNum, int preferred, int * found);
+
+
+//
+// These two functions are mainly to support the controller configuration GUI.
+// Could just as easily go there as well (and be better placed there).
+//
+void SaveProfiles(void)
+{
+ numberOfProfilesSave = numberOfProfiles;
+ memcpy(&profileBackup, &profile, sizeof(Profile) * MAX_PROFILES);
+}
+
+
+void RestoreProfiles(void)
+{
+ memcpy(&profile, &profileBackup, sizeof(Profile) * MAX_PROFILES);
+ numberOfProfiles = numberOfProfilesSave;
+}
+
+
void ReadProfiles(QSettings * set)
{
// Assume no profiles, until we read them
{
set->setArrayIndex(i - 1);
strcpy(deviceNames[i], set->value("deviceName").toString().toAscii().data());
-//printf("Read device name: %s\n", deviceNames[i]);
+#ifdef DEBUG_PROFILES
+printf("Read device name: %s\n", deviceNames[i]);
+#endif
}
set->endArray();
numberOfProfiles = set->beginReadArray("profiles");
-//printf("Number of profiles: %u\n", numberOfProfiles);
+#ifdef DEBUG_PROFILES
+printf("Number of profiles: %u\n", numberOfProfiles);
+#endif
for(int i=0; i<numberOfProfiles; i++)
{
set->setArrayIndex(i);
profile[i].device = set->value("deviceNum").toInt();
strcpy(profile[i].mapName, set->value("mapName").toString().toAscii().data());
- profile[i].preferredController = set->value("preferredController").toInt();
+ profile[i].preferredSlot = set->value("preferredSlot").toInt();
for(int j=0; j<21; j++)
{
QString string = QString("map%1").arg(j);
profile[i].map[j] = set->value(string).toInt();
}
-//printf("Profile #%u: device=%u (%s)\n", i, profile[i].device, deviceNames[profile[i].device]);
+#ifdef DEBUG_PROFILES
+printf("Profile #%u: device=%u (%s)\n", i, profile[i].device, deviceNames[profile[i].device]);
+#endif
}
set->endArray();
-//printf("Number of profiles found: %u\n", numberOfProfiles);
+#ifdef DEBUG_PROFILES
+printf("Number of profiles found: %u\n", numberOfProfiles);
+#endif
// Set up a reasonable default if no profiles were found
if (numberOfProfiles == 0)
{
-//printf("Setting up default profile...\n");
+#ifdef DEBUG_PROFILES
+printf("Setting up default profile...\n");
+#endif
numberOfProfiles++;
profile[0].device = 0; // Keyboard is always device #0
strcpy(profile[0].mapName, "Default");
- profile[0].preferredController = CONTROLLER1;
+ profile[0].preferredSlot = CONTROLLER1;
for(int i=0; i<21; i++)
profile[0].map[i] = defaultMap[i];
set->setArrayIndex(i);
set->setValue("deviceNum", profile[i].device);
set->setValue("mapName", profile[i].mapName);
- set->setValue("preferredController", profile[i].preferredController);
+ set->setValue("preferredSlot", profile[i].preferredSlot);
for(int j=0; j<21; j++)
{
}
+int GetFreeProfile(void)
+{
+ // Check for too many, return -1 if so
+ if (numberOfProfiles == MAX_PROFILES)
+ return -1;
+
+ int profileNum = numberOfProfiles;
+ numberOfProfiles++;
+ return profileNum;
+}
+
+
+void DeleteProfile(int profileToDelete)
+{
+ // Sanity check
+ if (profileToDelete >= numberOfProfiles)
+ return;
+
+ // Trivial case: Profile at end of the array
+ if (profileToDelete == (numberOfProfiles - 1))
+ {
+ numberOfProfiles--;
+ return;
+ }
+
+// memmove(dest, src, bytesToMove);
+ memmove(&profile[profileToDelete], &profile[profileToDelete + 1], ((numberOfProfiles - 1) - profileToDelete) * sizeof(Profile));
+ numberOfProfiles--;
+}
+
+
int FindDeviceNumberForName(const char * name)
{
for(int i=0; i<numberOfDevices; i++)
{
if (strcmp(deviceNames[i], name) == 0)
+#ifdef DEBUG_PROFILES
+{
+printf("PROFILE: Found device #%i for name (%s)...\n", i, name);
+#endif
return i;
+#ifdef DEBUG_PROFILES
+}
+#endif
}
if (numberOfDevices == MAX_DEVICES)
return -1;
+#ifdef DEBUG_PROFILES
+printf("Device '%s' not found, creating device...\n", name);
+#endif
// If the device wasn't found, it must be new; so add it to the list.
int deviceNum = numberOfDevices;
deviceNames[deviceNum][127] = 0;
for(int i=0; i<numberOfProfiles; i++)
{
- if (profile[i].device == -1)
- continue;
+//This should *never* be the case--all profiles in list are *good*
+// if (profile[i].device == -1)
+// continue;
if (profile[i].device == deviceNum)
{
{
profile[numberOfProfiles].device = deviceNum;
strcpy(profile[numberOfProfiles].mapName, "Default");
- profile[numberOfProfiles].preferredController = CONTROLLER1;
+ profile[numberOfProfiles].preferredSlot = CONTROLLER1;
for(int i=0; i<21; i++)
profile[numberOfProfiles].map[i] = defaultMap[i];
}
+// N.B.: Unused
+int FindUsableProfiles(QComboBox * combo)
+{
+ int found = 0;
+
+ // Check for device #0 (keyboard) profiles first
+ for(int j=0; j<numberOfProfiles; j++)
+ {
+ // Check for device *and* usable configuration
+ if ((profile[j].device == 0) && (profile[j].preferredSlot))
+ {
+ combo->addItem(QString("Keyboard::%1").arg(profile[j].mapName), j);
+ found++;
+ }
+ }
+
+ // Check for connected host devices next
+ for(int i=0; i<Gamepad::numJoysticks; i++)
+ {
+ int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
+
+ for(int j=0; j<numberOfProfiles; j++)
+ {
+ if ((profile[j].device == deviceNum) && (profile[j].preferredSlot))
+ {
+ combo->addItem(QString("%1::%2").arg(Gamepad::GetJoystickName(i)).arg(profile[j].mapName), j);
+ found++;
+ }
+ }
+ }
+
+ return found;
+}
+
+
bool ConnectProfileToController(int profileNum, int controllerNum)
{
+ // Sanity checks...
+ if (profileNum < 0)
+ return false;
+
if (profile[profileNum].device == -1)
return false;
for(int i=0; i<21; i++)
dest[i] = profile[profileNum].map[i];
-printf("Successfully mapped device '%s' (%s) to controller #%u...\n", deviceNames[profile[profileNum].device], profile[profileNum].mapName, controllerNum);
+ WriteLog("PROFILE: Successfully mapped device '%s' (%s) to controller #%u...\n", deviceNames[profile[profileNum].device], profile[profileNum].mapName, controllerNum);
return true;
}
-//
-// This is a pretty crappy way of doing autodetection. What it does is scan for
-// keyboard profiles first, then look for plugged in gamepads next. If more
-// than one plugged in gamepad matches a preferred controller slot, the last
-// one found is chosen.
-//
-// There has to be a better way to do this, I just can't think of what it
-// should be ATM... :-P
-//
/*
-Also, there is a problem with this approach and having multiple devices
-that are the same. Currently, if two of the same device are plugged in
-and the profile is set to both controllers, it will broadcast buttons
-pressed from either gamepad, no matter who is pressing them. This is
-BAD(tm). [Not true, but there's a different problem described under 'How to
-solve?', so GOOD(tm).]
-
-Also, the gamepad logic doesn't distinguish inputs by controller, it just
-grabs them all regardless. This is also BAD(tm). [Actually, it doesn't. It
-properly segregates the inputs. So this is GOOD(tm).]
-
-How to solve?
-
-Seems there's yet ANOTHER dimension to all this: The physical gamepads
-plugged into their ports. Now the device # can map these fine if they're
-different, but we still run into problems with the handling in the MainWin
-because it's hardwired to take pad 0 in slot 0 and pad 1 in slot 1. If you have
-them configured other than this, you won't get anything. So we need to also
-map the physical devices to their respective slots.
+One more stab at this...
+
+ - Connect keyboard to slot #0.
+ - Loop thru all connected devices. For each device:
+ - Grab all profiles for the device. For each profile:
+ - Check to see what its preferred device is.
+ - If PD is slot #0, see if slot is already taken (gamepadIDSlot1 != -1).
+ If not taken, take it; otherwise put in list to tell user to solve the
+ conflict for us.
+ - If the slot is already taken and *it's the same device* as the one
+ we're looking at, set it in slot #1.
+ - If PD is slot #1, see if slot is already taken. If not, take it;
+ otherwise, put in list to tell user to solve conflict for us.
+ - If PD is slot #0 & #1, see if either is already taken. Try #0 first,
+ then try #1. If both are already taken, skip it. Do this *after* we've
+ connected devices with preferred slots.
*/
void AutoConnectProfiles(void)
{
- int controller1Profile = -1;
- int controller2Profile = -1;
+// int foundProfiles[MAX_PROFILES];
+ controller1Profile = -1;
+ controller2Profile = -1;
+ gamepadIDSlot1 = -1;
+ gamepadIDSlot2 = -1;
+
+ // Connect the keyboard automagically only if no gamepads are plugged in.
+ // Otherwise, check after all other devices have been checked, then try to
+ // add it in.
+ if (Gamepad::numJoysticks == 0)
+ {
+ ConnectProfileToDevice(0);
+ return;
+ }
- // Nothing plugged in, we fall back to the default keyboard device profiles
-// if (Gamepad::numJoysticks == 0)
+ // Connect the profiles that prefer a slot, if any.
+ // N.B.: Conflicts are detected, but ignored. 1st controller to grab a
+ // preferred slot gets it. :-P
+ for(int i=0; i<Gamepad::numJoysticks; i++)
{
-// Check for Keyboard device first, if anything else is plugged in it will
-// default to it instead
- for(int i=0; i<numberOfProfiles; i++)
+ int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
+// bool p1Overwriteable =
+
+ for(int j=0; j<numberOfProfiles; j++)
{
- // Skip profile if it's not Keyboard device
- if (profile[i].device != 0)
+ if (deviceNum != profile[j].device)
continue;
- if (profile[i].preferredController & CONTROLLER1)
- controller1Profile = i;
+ int slot = profile[j].preferredSlot;
- if (profile[i].preferredController & CONTROLLER2)
- controller2Profile = i;
+ if (slot == CONTROLLER1)
+ {
+ if (gamepadIDSlot1 == -1)
+ controller1Profile = j, gamepadIDSlot1 = i;
+ else
+ {
+ // Autoresolve simple conflict: two controllers sharing one
+ // profile mapped to slot #0.
+ if ((deviceNum == profile[controller1Profile].device) && (controller2Profile == -1))
+ controller2Profile = j, gamepadIDSlot2 = i;
+ else
+ ; // Alert user to conflict and ask to resolve
+ }
+ }
+ else if (slot == CONTROLLER2)
+ {
+ if (gamepadIDSlot2 == -1)
+ controller2Profile = j, gamepadIDSlot2 = i;
+ else
+ {
+ // Autoresolve simple conflict: two controllers sharing one
+ // profile mapped to slot #1.
+ if ((deviceNum == profile[controller2Profile].device) && (controller1Profile == -1))
+ controller1Profile = j, gamepadIDSlot1 = i;
+ else
+ ; // Alert user to conflict and ask to resolve
+ }
+ }
}
}
-// else
+
+ // Connect the "don't care" states, if any. We don't roll it into the above,
+ // because it can override the profiles that have a definite preference.
+ // These should be lowest priority.
+ for(int i=0; i<Gamepad::numJoysticks; i++)
{
-//printf("Number of gamepads found: %u\n", Gamepad::numJoysticks);
- for(int i=0; i<Gamepad::numJoysticks; i++)
+ int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
+
+ for(int j=0; j<numberOfProfiles; j++)
{
- int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
-//printf("Attempting to find valid gamepad profile. Device=%u\n", deviceNum);
+ if (deviceNum != profile[j].device)
+ continue;
+
+ int slot = profile[j].preferredSlot;
- for(int j=0; j<numberOfProfiles; j++)
+ if (slot == (CONTROLLER1 | CONTROLLER2))
{
- // Skip profile if it's not discovered device
- if (profile[j].device != deviceNum)
- continue;
+ if (gamepadIDSlot1 == -1)
+ controller1Profile = j, gamepadIDSlot1 = i;
+ else if (gamepadIDSlot2 == -1)
+ controller2Profile = j, gamepadIDSlot2 = i;
+ }
+ }
+ }
- if (profile[j].preferredController & CONTROLLER1)
- controller1Profile = j;
+ // Connect the keyboard device (lowest priority)
+ // N.B.: The keyboard is always mapped to profile #0, so we can locate it
+ // easily. :-)
+ int slot = profile[0].preferredSlot;
- if (profile[j].preferredController & CONTROLLER2)
- controller2Profile = j;
- }
+ if ((slot == CONTROLLER1) && (gamepadIDSlot1 == -1))
+ controller1Profile = 0;
+ else if ((slot == CONTROLLER2) && (gamepadIDSlot2 == -1))
+ controller2Profile = 0;
+ else if (slot == (CONTROLLER1 | CONTROLLER2))
+ {
+ if (gamepadIDSlot1 == -1)
+ controller1Profile = 0;
+ else if (gamepadIDSlot2 == -1)
+ controller2Profile = 0;
+ }
+
+ // Finally, attempt to connect profiles to controllers
+ ConnectProfileToController(controller1Profile, 0);
+ ConnectProfileToController(controller2Profile, 1);
+}
+
+
+int ConnectProfileToDevice(int deviceNum, int gamepadID/*= -1*/)
+{
+// bool found1 = false;
+// bool found2 = false;
+ int numberFoundForController1 = 0;
+ int numberFoundForController2 = 0;
+
+ for(int i=0; i<numberOfProfiles; i++)
+ {
+ // Skip profile if it's not our device
+ if (profile[i].device != deviceNum)
+ continue;
+
+ if (profile[i].preferredSlot & CONTROLLER1)
+ {
+ controller1Profile = i;
+ gamepadIDSlot1 = gamepadID;
+// found1 = true;
+ numberFoundForController1++;
+ }
+
+ if (profile[i].preferredSlot & CONTROLLER2)
+ {
+ controller2Profile = i;
+ gamepadIDSlot2 = gamepadID;
+// found2 = true;
+ numberFoundForController2++;
}
}
- if (controller1Profile != -1)
- ConnectProfileToController(controller1Profile, 0);
+// return found;
+ return numberFoundForController1 + numberFoundForController2;
+}
+
+
+// N.B.: Unused
+int FindProfileForDevice(int deviceNum, int preferred, int * found)
+{
+ int numFound = 0;
+
+ for(int i=0; i<numberOfProfiles; i++)
+ {
+ // Return the profile only if it matches the passed in device and
+ // matches the passed in preference...
+ if ((profile[i].device == deviceNum) && (profile[i].preferredSlot == preferred))
+ found[numFound++] = i;
+ }
- if (controller2Profile != -1)
- ConnectProfileToController(controller2Profile, 1);
+ return numFound;
}
+
+//
+// Also note that we have the intersection of three things here: One the one
+// hand, we have the detected joysticks with their IDs (typically in the range
+// of 0-7), we have our gamepad profiles and their IDs (typically can have up to
+// 64 of them), and we have our gamepad slots that the detected joysticks can be
+// connected to.
+//
+// So, when the user plugs in a gamepad, it gets a joystick ID, then the profile
+// manager checks to see if a profile (or profiles) for it exists. If so, then
+// it assigns that joystick ID to a gamepad slot, based upon what the user
+// requested for that profile.
+//
+// A problem (perhaps) arises when you have more than one profile for a certain
+// device, how do you know which one to use? Perhaps you have a field in the
+// profile saying that you use this profile 1st, that one 2nd, and so on...
+//
+// Some use cases, and how to resolve them:
+//
+// - User has two of the same device, and plugs them both in. There is only one
+// profile. In this case, the sane thing to do is ignore the "preferred slot"
+// of the dialog and use the same profile for both controllers, and plug them
+// both into slot #0 and #1.
+// - User has one device, and plugs it in. There are two profiles. In this case,
+// the profile chosen should be based upon the "preferred slot", with slot #0
+// being the winner. If both profiles are set for slot #0, ask the user which
+// profile to use, and set a flag in the profile to say that it is a preferred
+// profile for that device.
+// - In any case where there are conflicts, the user must be consulted and sane
+// defaults used.
+//