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