]> Shamusworld >> Repos - virtualjaguar/blob - src/gui/profile.cpp
Fixed controller profile system.
[virtualjaguar] / src / gui / profile.cpp
1 //
2 // profile.cpp - Global profile storage/definition/manipulation
3 //
4 // by James Hammons
5 // (C) 2013 Underground Software
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // Who  When        What
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  05/01/2013  Created this file
12 // JLH  10/02/2014  Finally fixed stuff so it works the way it should
13 //
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.
18 //
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.
26 //
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!
30 //
31
32 #include "profile.h"
33 #include <QtGui>
34 #include "gamepad.h"
35 #include "log.h"
36 #include "settings.h"
37
38
39 //#define DEBUG_PROFILES
40 #define MAX_DEVICES  64
41
42
43 Profile profile[MAX_PROFILES];
44 Profile profileBackup[MAX_PROFILES];
45 int controller1Profile;
46 int controller2Profile;
47 int gamepadIDSlot1;
48 int gamepadIDSlot2;
49 int numberOfProfiles;
50 int numberOfDevices;
51 char deviceNames[MAX_DEVICES][128];
52
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'
57 };
58
59
60 // Function Prototypes
61 int ConnectProfileToDevice(int deviceNum, int gamepadID = -1);
62 int FindProfileForDevice(int deviceNum, int preferred, int * found);
63
64
65 //
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).
68 //
69 void SaveProfiles(void)
70 {
71         memcpy(&profileBackup, &profile, sizeof(Profile) * MAX_PROFILES);
72 }
73
74
75 void RestoreProfiles(void)
76 {
77         memcpy(&profile, &profileBackup, sizeof(Profile) * MAX_PROFILES);
78 }
79
80
81 void ReadProfiles(QSettings * set)
82 {
83         // Assume no profiles, until we read them
84         numberOfProfiles = 0;
85
86         // There is always at least one device present, and it's the keyboard
87         // (hey, we're PC centric here ;-)
88         numberOfDevices = 1;
89         strcpy(deviceNames[0], "Keyboard");
90
91         // Read the rest of the devices (if any)
92         numberOfDevices += set->beginReadArray("devices");
93
94         for(int i=1; i<numberOfDevices; i++)
95         {
96                 set->setArrayIndex(i - 1);
97                 strcpy(deviceNames[i], set->value("deviceName").toString().toAscii().data());
98 #ifdef DEBUG_PROFILES
99 printf("Read device name: %s\n", deviceNames[i]);
100 #endif
101         }
102
103         set->endArray();
104         numberOfProfiles = set->beginReadArray("profiles");
105 #ifdef DEBUG_PROFILES
106 printf("Number of profiles: %u\n", numberOfProfiles);
107 #endif
108
109         for(int i=0; i<numberOfProfiles; i++)
110         {
111                 set->setArrayIndex(i);
112                 profile[i].device = set->value("deviceNum").toInt();
113                 strcpy(profile[i].mapName, set->value("mapName").toString().toAscii().data());
114                 profile[i].preferredSlot = set->value("preferredSlot").toInt();
115
116                 for(int j=0; j<21; j++)
117                 {
118                         QString string = QString("map%1").arg(j);
119                         profile[i].map[j] = set->value(string).toInt();
120                 }
121 #ifdef DEBUG_PROFILES
122 printf("Profile #%u: device=%u (%s)\n", i, profile[i].device, deviceNames[profile[i].device]);
123 #endif
124         }
125
126         set->endArray();
127
128 #ifdef DEBUG_PROFILES
129 printf("Number of profiles found: %u\n", numberOfProfiles);
130 #endif
131         // Set up a reasonable default if no profiles were found
132         if (numberOfProfiles == 0)
133         {
134 #ifdef DEBUG_PROFILES
135 printf("Setting up default profile...\n");
136 #endif
137                 numberOfProfiles++;
138                 profile[0].device = 0;  // Keyboard is always device #0
139                 strcpy(profile[0].mapName, "Default");
140                 profile[0].preferredSlot = CONTROLLER1;
141
142                 for(int i=0; i<21; i++)
143                         profile[0].map[i] = defaultMap[i];
144         }
145 }
146
147
148 void WriteProfiles(QSettings * set)
149 {
150 #if 0
151         // Don't write anything for now...
152         return;
153 #endif
154         // NB: Should only do this if something changed; otherwise, no need to do
155         //     this.
156         set->beginWriteArray("devices");
157
158         for(int i=1; i<numberOfDevices; i++)
159         {
160                 set->setArrayIndex(i - 1);
161                 set->setValue("deviceName", deviceNames[i]);
162         }
163
164         set->endArray();
165         set->beginWriteArray("profiles");
166
167         for(int i=0; i<numberOfProfiles; i++)
168         {
169                 set->setArrayIndex(i);
170                 set->setValue("deviceNum", profile[i].device);
171                 set->setValue("mapName", profile[i].mapName);
172                 set->setValue("preferredSlot", profile[i].preferredSlot);
173
174                 for(int j=0; j<21; j++)
175                 {
176                         QString string = QString("map%1").arg(j);
177                         set->setValue(string, profile[i].map[j]);
178                 }
179         }
180
181         set->endArray();
182 }
183
184
185 int GetFreeProfile(void)
186 {
187         // Check for too many, return -1 if so
188         if (numberOfProfiles == MAX_PROFILES)
189                 return -1;
190
191         int profileNum = numberOfProfiles;
192         numberOfProfiles++;
193         return profileNum;
194 }
195
196
197 void DeleteProfile(int profileToDelete)
198 {
199         // Sanity check
200         if (profileToDelete >= numberOfProfiles)
201                 return;
202
203         // Trivial case: Profile at end of the array
204         if (profileToDelete == (numberOfProfiles - 1))
205         {
206                 numberOfProfiles--;
207                 return;
208         }
209
210 //      memmove(dest, src, bytesToMove);
211         memmove(&profile[profileToDelete], &profile[profileToDelete + 1], ((numberOfProfiles - 1) - profileToDelete) * sizeof(Profile));
212         numberOfProfiles--;
213 }
214
215
216 int FindDeviceNumberForName(const char * name)
217 {
218         for(int i=0; i<numberOfDevices; i++)
219         {
220                 if (strcmp(deviceNames[i], name) == 0)
221 #ifdef DEBUG_PROFILES
222 {
223 printf("PROFILE: Found device #%i for name (%s)...\n", i, name);
224 #endif
225                         return i;
226 #ifdef DEBUG_PROFILES
227 }
228 #endif
229         }
230
231         if (numberOfDevices == MAX_DEVICES)
232                 return -1;
233
234 #ifdef DEBUG_PROFILES
235 printf("Device '%s' not found, creating device...\n", name);
236 #endif
237         // If the device wasn't found, it must be new; so add it to the list.
238         int deviceNum = numberOfDevices;
239         deviceNames[deviceNum][127] = 0;
240         strncpy(deviceNames[deviceNum], name, 127);
241         numberOfDevices++;
242
243         return deviceNum;
244 }
245
246
247 int FindMappingsForDevice(int deviceNum, QComboBox * combo)
248 {
249         int found = 0;
250
251         for(int i=0; i<numberOfProfiles; i++)
252         {
253 //This should *never* be the case--all profiles in list are *good*
254 //              if (profile[i].device == -1)
255 //                      continue;
256
257                 if (profile[i].device == deviceNum)
258                 {
259                         combo->addItem(profile[i].mapName, i);
260                         found++;
261                 }
262         }
263
264         // If no mappings were found, create a default one for it
265         if (found == 0)
266         {
267                 profile[numberOfProfiles].device = deviceNum;
268                 strcpy(profile[numberOfProfiles].mapName, "Default");
269                 profile[numberOfProfiles].preferredSlot = CONTROLLER1;
270
271                 for(int i=0; i<21; i++)
272                         profile[numberOfProfiles].map[i] = defaultMap[i];
273
274                 combo->addItem(profile[numberOfProfiles].mapName, numberOfProfiles);
275                 numberOfProfiles++;
276                 found++;
277         }
278
279         return found;
280 }
281
282
283 // N.B.: Unused
284 int FindUsableProfiles(QComboBox * combo)
285 {
286         int found = 0;
287
288         // Check for device #0 (keyboard) profiles first
289         for(int j=0; j<numberOfProfiles; j++)
290         {
291                 // Check for device *and* usable configuration
292                 if ((profile[j].device == 0) && (profile[j].preferredSlot))
293                 {
294                         combo->addItem(QString("Keyboard::%1").arg(profile[j].mapName), j);
295                         found++;
296                 }
297         }
298
299         // Check for connected host devices next
300         for(int i=0; i<Gamepad::numJoysticks; i++)
301         {
302                 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
303
304                 for(int j=0; j<numberOfProfiles; j++)
305                 {
306                         if ((profile[j].device == deviceNum) && (profile[j].preferredSlot))
307                         {
308                                 combo->addItem(QString("%1::%2").arg(Gamepad::GetJoystickName(i)).arg(profile[j].mapName), j);
309                                 found++;
310                         }
311                 }
312         }
313
314         return found;
315 }
316
317
318 bool ConnectProfileToController(int profileNum, int controllerNum)
319 {
320         // Sanity checks...
321         if (profileNum < 0)
322                 return false;
323
324         if (profile[profileNum].device == -1)
325                 return false;
326
327         if (controllerNum < 0 || controllerNum > 2)
328                 return false;
329
330         uint32_t * dest = (controllerNum == 0 ? &vjs.p1KeyBindings[0] : &vjs.p2KeyBindings[0]);
331
332         for(int i=0; i<21; i++)
333                 dest[i] = profile[profileNum].map[i];
334
335         WriteLog("PROFILE: Successfully mapped device '%s' (%s) to controller #%u...\n", deviceNames[profile[profileNum].device], profile[profileNum].mapName, controllerNum);
336         return true;
337 }
338
339
340 /*
341 One more stab at this...
342
343  -  Connect keyboard to slot #0.
344  -  Loop thru all connected devices. For each device:
345     -  Grab all profiles for the device. For each profile:
346        -  Check to see what its preferred device is.
347        -  If PD is slot #0, see if slot is already taken (gamepadIDSlot1 != -1).
348           If not taken, take it; otherwise put in list to tell user to solve the
349           conflict for us.
350           -  If the slot is already taken and *it's the same device* as the one
351              we're looking at, set it in slot #1.
352        -  If PD is slot #1, see if slot is already taken. If not, take it;
353           otherwise, put in list to tell user to solve conflict for us.
354        -  If PD is slot #0 & #1, see if either is already taken. Try #0 first,
355           then try #1. If both are already taken, skip it. Do this *after* we've
356           connected devices with preferred slots.
357 */
358 void AutoConnectProfiles(void)
359 {
360         int foundProfiles[MAX_PROFILES];
361         controller1Profile = -1;
362         controller2Profile = -1;
363         gamepadIDSlot1 = -1;
364         gamepadIDSlot2 = -1;
365
366         // Connect keyboard devices first... (N.B.: this leaves gampadIDSlot1 at -1,
367         // so it can be overridden by plugged-in gamepads.)
368         ConnectProfileToDevice(0);
369
370         // Connect the profiles that prefer a slot, if any.
371         // N.B.: Conflicts are detected, but ignored. 1st controller to grab a
372         //       preferred slot gets it. :-P
373         for(int i=0; i<Gamepad::numJoysticks; i++)
374         {
375                 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
376 //              bool p1Overwriteable = 
377
378                 for(int j=0; j<numberOfProfiles; j++)
379                 {
380                         if (deviceNum != profile[j].device)
381                                 continue;
382
383                         int slot = profile[j].preferredSlot;
384
385                         if (slot == CONTROLLER1)
386                         {
387                                 if (gamepadIDSlot1 == -1)
388                                         controller1Profile = j, gamepadIDSlot1 = i;
389                                 else
390                                 {
391                                         // Autoresolve simple conflict: two controllers sharing one
392                                         // profile mapped to slot #0.
393                                         if ((deviceNum == profile[controller1Profile].device) && (controller2Profile == -1))
394                                                 controller2Profile = j, gamepadIDSlot2 = i;
395                                         else
396                                                 ; // Alert user to conflict and ask to resolve
397                                 }
398                         }
399                         else if (slot == CONTROLLER2)
400                         {
401                                 if (gamepadIDSlot2 == -1)
402                                         controller2Profile = j, gamepadIDSlot2 = i;
403                                 else
404                                 {
405                                         // Autoresolve simple conflict: two controllers sharing one
406                                         // profile mapped to slot #1.
407                                         if ((deviceNum == profile[controller2Profile].device) && (controller1Profile == -1))
408                                                 controller1Profile = j, gamepadIDSlot1 = i;
409                                         else
410                                                 ; // Alert user to conflict and ask to resolve
411                                 }
412                         }
413                 }
414         }
415
416         // Connect the "don't care" states, if any. We don't roll it into the above,
417         // because it can override the profiles that have a definite preference.
418         // These should be lowest priority.
419         for(int i=0; i<Gamepad::numJoysticks; i++)
420         {
421                 int deviceNum = FindDeviceNumberForName(Gamepad::GetJoystickName(i));
422
423                 for(int j=0; j<numberOfProfiles; j++)
424                 {
425                         if (deviceNum != profile[j].device)
426                                 continue;
427
428                         int slot = profile[j].preferredSlot;
429
430                         if (slot == (CONTROLLER1 | CONTROLLER2))
431                         {
432                                 if (gamepadIDSlot1 == -1)
433                                         controller1Profile = j, gamepadIDSlot1 = i;
434                                 else if (gamepadIDSlot2 == -1)
435                                         controller2Profile = j, gamepadIDSlot2 = i;
436                         }
437                 }
438         }
439
440         // Finally, attempt to connect profiles to controllers
441         ConnectProfileToController(controller1Profile, 0);
442         ConnectProfileToController(controller2Profile, 1);
443 }
444
445
446 int ConnectProfileToDevice(int deviceNum, int gamepadID/*= -1*/)
447 {
448 //      bool found1 = false;
449 //      bool found2 = false;
450         int numberFoundForController1 = 0;
451         int numberFoundForController2 = 0;
452
453         for(int i=0; i<numberOfProfiles; i++)
454         {
455                 // Skip profile if it's not our device
456                 if (profile[i].device != deviceNum)
457                         continue;
458
459                 if (profile[i].preferredSlot & CONTROLLER1)
460                 {
461                         controller1Profile = i;
462                         gamepadIDSlot1 = gamepadID;
463 //                      found1 = true;
464                         numberFoundForController1++;
465                 }
466
467                 if (profile[i].preferredSlot & CONTROLLER2)
468                 {
469                         controller2Profile = i;
470                         gamepadIDSlot2 = gamepadID;
471 //                      found2 = true;
472                         numberFoundForController2++;
473                 }
474         }
475
476 //      return found;
477         return numberFoundForController1 + numberFoundForController2;
478 }
479
480
481 // N.B.: Unused
482 int FindProfileForDevice(int deviceNum, int preferred, int * found)
483 {
484         int numFound = 0;
485
486         for(int i=0; i<numberOfProfiles; i++)
487         {
488                 // Return the profile only if it matches the passed in device and
489                 // matches the passed in preference...
490                 if ((profile[i].device == deviceNum) && (profile[i].preferredSlot == preferred))
491                         found[numFound++] = i;
492         }
493
494         return numFound;
495 }
496
497
498 //
499 // Also note that we have the intersection of three things here: One the one
500 // hand, we have the detected joysticks with their IDs (typically in the range
501 // of 0-7), we have our gamepad profiles and their IDs (typically can have up to
502 // 64 of them), and we have our gamepad slots that the detected joysticks can be
503 // connected to.
504 //
505 // So, when the user plugs in a gamepad, it gets a joystick ID, then the profile
506 // manager checks to see if a profile (or profiles) for it exists. If so, then
507 // it assigns that joystick ID to a gamepad slot, based upon what the user
508 // requested for that profile.
509 //
510 // A problem (perhaps) arises when you have more than one profile for a certain
511 // device, how do you know which one to use? Perhaps you have a field in the
512 // profile saying that you use this profile 1st, that one 2nd, and so on...
513 //
514 // Some use cases, and how to resolve them:
515 //
516 // - User has two of the same device, and plugs them both in. There is only one
517 //   profile. In this case, the sane thing to do is ignore the "preferred slot"
518 //   of the dialog and use the same profile for both controllers, and plug them
519 //   both into slot #0 and #1.
520 // - User has one device, and plugs it in. There are two profiles. In this case,
521 //   the profile chosen should be based upon the "preferred slot", with slot #0
522 //   being the winner. If both profiles are set for slot #0, ask the user which
523 //   profile to use, and set a flag in the profile to say that it is a preferred
524 //   profile for that device.
525 // - In any case where there are conflicts, the user must be consulted and sane
526 //   defaults used.
527 //