2 // profile.cpp - Global profile storage/definition/manipulation
5 // (C) 2013 Underground Software
7 // JLH = James Hammons <jlhamm@acm.org>
10 // --- ---------- ------------------------------------------------------------
11 // JLH 05/01/2013 Created this file
12 // JLH 10/02/2014 Finally fixed stuff so it works the way it should
14 // This is a profile database with two parts: One, a list of devices, and two,
15 // a list of profiles each containing a pointer to the device list, and map
16 // name, a preferred slot #, and a key/button map. All the heavy lifting (incl.
17 // autoconnection of devices to profiles to slots) is done here.
19 // Basically, how this works is that we connect the device the user plugs into
20 // the computer to a profile in the database to a slot in the virtual Jaguar.
21 // Hopefully the configuration that the user gives us is sane enough for us to
22 // figure out how to do the right thing! By default, there is always a keyboard
23 // device plugged in; any other device that gets plugged in and wants to be in
24 // slot #0 can override it. This is so there is always a sane configuration if
25 // nothing is plugged in.
27 // Devices go into the database when the user plugs them in and runs VJ, and
28 // subsequently does anything to alter any of the existing profiles. Once a
29 // device has been seen, it can't be unseen!
38 //#define DEBUG_PROFILES
39 #define MAX_DEVICES 64
42 Profile profile[MAX_PROFILES];
43 Profile profileBackup[MAX_PROFILES];
44 int controller1Profile;
45 int controller2Profile;
50 char deviceNames[MAX_DEVICES][128];
51 static int numberOfProfilesSave;
53 // This is so that new devices have something reasonable to show for default
54 uint32_t defaultMap[21] = {
55 'S', 'X', 'Z', 'C', '-','7', '4', '1', '0', '8', '5', '2', '=', '9', '6',
56 '3', 'L', 'K', 'J', 'O', 'P'
60 // Function Prototypes
61 int ConnectProfileToDevice(int deviceNum, int gamepadID = -1);
62 int FindProfileForDevice(int deviceNum, int preferred, int * found);
66 // These two functions are mainly to support the controller configuration GUI.
67 // Could just as easily go there as well (and be better placed there).
69 void SaveProfiles(void)
71 numberOfProfilesSave = numberOfProfiles;
72 memcpy(&profileBackup, &profile, sizeof(Profile) * MAX_PROFILES);
76 void RestoreProfiles(void)
78 memcpy(&profile, &profileBackup, sizeof(Profile) * MAX_PROFILES);
79 numberOfProfiles = numberOfProfilesSave;
83 void ReadProfiles(QSettings * set)
85 // Assume no profiles, until we read them
88 // There is always at least one device present, and it's the keyboard
89 // (hey, we're PC centric here ;-)
91 strcpy(deviceNames[0], "Keyboard");
93 // Read the rest of the devices (if any)
94 numberOfDevices += set->beginReadArray("devices");
96 for(int i=1; i<numberOfDevices; i++)
98 set->setArrayIndex(i - 1);
99 strcpy(deviceNames[i], set->value("deviceName").toString().toUtf8().data());
100 #ifdef DEBUG_PROFILES
101 printf("Read device name: %s\n", deviceNames[i]);
106 numberOfProfiles = set->beginReadArray("profiles");
107 #ifdef DEBUG_PROFILES
108 printf("Number of profiles: %u\n", numberOfProfiles);
111 for(int i=0; i<numberOfProfiles; i++)
113 set->setArrayIndex(i);
114 profile[i].device = set->value("deviceNum").toInt();
115 strcpy(profile[i].mapName, set->value("mapName").toString().toUtf8().data());
116 profile[i].preferredSlot = set->value("preferredSlot").toInt();
118 for(int j=0; j<21; j++)
120 QString string = QString("map%1").arg(j);
121 profile[i].map[j] = set->value(string).toInt();
123 #ifdef DEBUG_PROFILES
124 printf("Profile #%u: device=%u (%s)\n", i, profile[i].device, deviceNames[profile[i].device]);
130 #ifdef DEBUG_PROFILES
131 printf("Number of profiles found: %u\n", numberOfProfiles);
133 // Set up a reasonable default if no profiles were found
134 if (numberOfProfiles == 0)
136 #ifdef DEBUG_PROFILES
137 printf("Setting up default profile...\n");
140 profile[0].device = 0; // Keyboard is always device #0
141 strcpy(profile[0].mapName, "Default");
142 profile[0].preferredSlot = CONTROLLER1;
144 for(int i=0; i<21; i++)
145 profile[0].map[i] = defaultMap[i];
150 void WriteProfiles(QSettings * set)
153 // Don't write anything for now...
156 // NB: Should only do this if something changed; otherwise, no need to do
158 set->beginWriteArray("devices");
160 for(int i=1; i<numberOfDevices; i++)
162 set->setArrayIndex(i - 1);
163 set->setValue("deviceName", deviceNames[i]);
167 set->beginWriteArray("profiles");
169 for(int i=0; i<numberOfProfiles; i++)
171 set->setArrayIndex(i);
172 set->setValue("deviceNum", profile[i].device);
173 set->setValue("mapName", profile[i].mapName);
174 set->setValue("preferredSlot", profile[i].preferredSlot);
176 for(int j=0; j<21; j++)
178 QString string = QString("map%1").arg(j);
179 set->setValue(string, profile[i].map[j]);
187 int GetFreeProfile(void)
189 // Check for too many, return -1 if so
190 if (numberOfProfiles == MAX_PROFILES)
193 int profileNum = numberOfProfiles;
199 void DeleteProfile(int profileToDelete)
202 if (profileToDelete >= numberOfProfiles)
205 // Trivial case: Profile at end of the array
206 if (profileToDelete == (numberOfProfiles - 1))
212 // memmove(dest, src, bytesToMove);
213 memmove(&profile[profileToDelete], &profile[profileToDelete + 1], ((numberOfProfiles - 1) - profileToDelete) * sizeof(Profile));
218 int FindDeviceNumberForName(const char * name)
220 for(int i=0; i<numberOfDevices; i++)
222 if (strcmp(deviceNames[i], name) == 0)
223 #ifdef DEBUG_PROFILES
225 printf("PROFILE: Found device #%i for name (%s)...\n", i, name);
228 #ifdef DEBUG_PROFILES
233 if (numberOfDevices == MAX_DEVICES)
236 #ifdef DEBUG_PROFILES
237 printf("Device '%s' not found, creating device...\n", name);
239 // If the device wasn't found, it must be new; so add it to the list.
240 int deviceNum = numberOfDevices;
241 deviceNames[deviceNum][127] = 0;
242 strncpy(deviceNames[deviceNum], name, 127);
249 int FindMappingsForDevice(int deviceNum, QComboBox * combo)
253 for(int i=0; i<numberOfProfiles; i++)
255 //This should *never* be the case--all profiles in list are *good*
256 // if (profile[i].device == -1)
259 if (profile[i].device == deviceNum)
261 combo->addItem(profile[i].mapName, i);
266 // If no mappings were found, create a default one for it
269 profile[numberOfProfiles].device = deviceNum;
270 strcpy(profile[numberOfProfiles].mapName, "Default");
271 profile[numberOfProfiles].preferredSlot = CONTROLLER1;
273 for(int i=0; i<21; i++)
274 profile[numberOfProfiles].map[i] = defaultMap[i];
276 combo->addItem(profile[numberOfProfiles].mapName, numberOfProfiles);
286 int FindUsableProfiles(QComboBox * combo)
290 // Check for device #0 (keyboard) profiles first
291 for(int j=0; j<numberOfProfiles; j++)
293 // Check for device *and* usable configuration
294 if ((profile[j].device == 0) && (profile[j].preferredSlot))
296 combo->addItem(QString("Keyboard::%1").arg(profile[j].mapName), j);
301 // Check for connected host devices next
302 for(int i=0; i<Gamepad::numJoysticks; i++)
304 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
306 for(int j=0; j<numberOfProfiles; j++)
308 if ((profile[j].device == deviceNum) && (profile[j].preferredSlot))
310 combo->addItem(QString("%1::%2").arg(Gamepad::GetJoystickName(i)).arg(profile[j].mapName), j);
320 bool ConnectProfileToController(int profileNum, int controllerNum)
326 if (profile[profileNum].device == -1)
329 if (controllerNum < 0 || controllerNum > 2)
332 uint32_t * dest = (controllerNum == 0 ? &vjs.p1KeyBindings[0] : &vjs.p2KeyBindings[0]);
334 for(int i=0; i<21; i++)
335 dest[i] = profile[profileNum].map[i];
337 WriteLog("PROFILE: Successfully mapped device '%s' (%s) to controller #%u...\n", deviceNames[profile[profileNum].device], profile[profileNum].mapName, controllerNum);
343 One more stab at this...
345 - Connect keyboard to slot #0.
346 - Loop thru all connected devices. For each device:
347 - Grab all profiles for the device. For each profile:
348 - Check to see what its preferred device is.
349 - If PD is slot #0, see if slot is already taken (gamepadIDSlot1 != -1).
350 If not taken, take it; otherwise put in list to tell user to solve the
352 - If the slot is already taken and *it's the same device* as the one
353 we're looking at, set it in slot #1.
354 - If PD is slot #1, see if slot is already taken. If not, take it;
355 otherwise, put in list to tell user to solve conflict for us.
356 - If PD is slot #0 & #1, see if either is already taken. Try #0 first,
357 then try #1. If both are already taken, skip it. Do this *after* we've
358 connected devices with preferred slots.
360 void AutoConnectProfiles(void)
362 // int foundProfiles[MAX_PROFILES];
363 controller1Profile = -1;
364 controller2Profile = -1;
368 // Connect the keyboard automagically only if no gamepads are plugged in.
369 // Otherwise, check after all other devices have been checked, then try to
371 if (Gamepad::numJoysticks == 0)
373 ConnectProfileToDevice(0);
377 // Connect the profiles that prefer a slot, if any.
378 // N.B.: Conflicts are detected, but ignored. 1st controller to grab a
379 // preferred slot gets it. :-P
380 for(int i=0; i<Gamepad::numJoysticks; i++)
382 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
383 // bool p1Overwriteable =
385 for(int j=0; j<numberOfProfiles; j++)
387 if (deviceNum != profile[j].device)
390 int slot = profile[j].preferredSlot;
392 if (slot == CONTROLLER1)
394 if (gamepadIDSlot1 == -1)
395 controller1Profile = j, gamepadIDSlot1 = i;
398 // Autoresolve simple conflict: two controllers sharing one
399 // profile mapped to slot #0.
400 if ((deviceNum == profile[controller1Profile].device) && (controller2Profile == -1))
401 controller2Profile = j, gamepadIDSlot2 = i;
403 ; // Alert user to conflict and ask to resolve
406 else if (slot == CONTROLLER2)
408 if (gamepadIDSlot2 == -1)
409 controller2Profile = j, gamepadIDSlot2 = i;
412 // Autoresolve simple conflict: two controllers sharing one
413 // profile mapped to slot #1.
414 if ((deviceNum == profile[controller2Profile].device) && (controller1Profile == -1))
415 controller1Profile = j, gamepadIDSlot1 = i;
417 ; // Alert user to conflict and ask to resolve
423 // Connect the "don't care" states, if any. We don't roll it into the above,
424 // because it can override the profiles that have a definite preference.
425 // These should be lowest priority.
426 for(int i=0; i<Gamepad::numJoysticks; i++)
428 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
430 for(int j=0; j<numberOfProfiles; j++)
432 if (deviceNum != profile[j].device)
435 int slot = profile[j].preferredSlot;
437 if (slot == (CONTROLLER1 | CONTROLLER2))
439 if (gamepadIDSlot1 == -1)
440 controller1Profile = j, gamepadIDSlot1 = i;
441 else if (gamepadIDSlot2 == -1)
442 controller2Profile = j, gamepadIDSlot2 = i;
447 // Connect the keyboard device (lowest priority)
448 // N.B.: The keyboard is always mapped to profile #0, so we can locate it
450 int slot = profile[0].preferredSlot;
452 if ((slot == CONTROLLER1) && (gamepadIDSlot1 == -1))
453 controller1Profile = 0;
454 else if ((slot == CONTROLLER2) && (gamepadIDSlot2 == -1))
455 controller2Profile = 0;
456 else if (slot == (CONTROLLER1 | CONTROLLER2))
458 if (gamepadIDSlot1 == -1)
459 controller1Profile = 0;
460 else if (gamepadIDSlot2 == -1)
461 controller2Profile = 0;
464 // Finally, attempt to connect profiles to controllers
465 ConnectProfileToController(controller1Profile, 0);
466 ConnectProfileToController(controller2Profile, 1);
470 int ConnectProfileToDevice(int deviceNum, int gamepadID/*= -1*/)
472 // bool found1 = false;
473 // bool found2 = false;
474 int numberFoundForController1 = 0;
475 int numberFoundForController2 = 0;
477 for(int i=0; i<numberOfProfiles; i++)
479 // Skip profile if it's not our device
480 if (profile[i].device != deviceNum)
483 if (profile[i].preferredSlot & CONTROLLER1)
485 controller1Profile = i;
486 gamepadIDSlot1 = gamepadID;
488 numberFoundForController1++;
491 if (profile[i].preferredSlot & CONTROLLER2)
493 controller2Profile = i;
494 gamepadIDSlot2 = gamepadID;
496 numberFoundForController2++;
501 return numberFoundForController1 + numberFoundForController2;
506 int FindProfileForDevice(int deviceNum, int preferred, int * found)
510 for(int i=0; i<numberOfProfiles; i++)
512 // Return the profile only if it matches the passed in device and
513 // matches the passed in preference...
514 if ((profile[i].device == deviceNum) && (profile[i].preferredSlot == preferred))
515 found[numFound++] = i;
523 // Also note that we have the intersection of three things here: One the one
524 // hand, we have the detected joysticks with their IDs (typically in the range
525 // of 0-7), we have our gamepad profiles and their IDs (typically can have up to
526 // 64 of them), and we have our gamepad slots that the detected joysticks can be
529 // So, when the user plugs in a gamepad, it gets a joystick ID, then the profile
530 // manager checks to see if a profile (or profiles) for it exists. If so, then
531 // it assigns that joystick ID to a gamepad slot, based upon what the user
532 // requested for that profile.
534 // A problem (perhaps) arises when you have more than one profile for a certain
535 // device, how do you know which one to use? Perhaps you have a field in the
536 // profile saying that you use this profile 1st, that one 2nd, and so on...
538 // Some use cases, and how to resolve them:
540 // - User has two of the same device, and plugs them both in. There is only one
541 // profile. In this case, the sane thing to do is ignore the "preferred slot"
542 // of the dialog and use the same profile for both controllers, and plug them
543 // both into slot #0 and #1.
544 // - User has one device, and plugs it in. There are two profiles. In this case,
545 // the profile chosen should be based upon the "preferred slot", with slot #0
546 // being the winner. If both profiles are set for slot #0, ask the user which
547 // profile to use, and set a flag in the profile to say that it is a preferred
548 // profile for that device.
549 // - In any case where there are conflicts, the user must be consulted and sane