2 // profile.cpp - Global profile storage/definition/manipulation
5 // (C) 2013 Underground Software
7 // JLH = James Hammons <jlhamm@acm.org>
8 // JPM = Jean-Paul Mari <djipi.mari@gmail.com>
11 // --- ---------- ------------------------------------------------------------
12 // JLH 05/01/2013 Created this file
13 // JLH 10/02/2014 Finally fixed stuff so it works the way it should
14 // JPM 06/06/2016 Visual Studio support
16 // This is a profile database with two parts: One, a list of devices, and two,
17 // a list of profiles each containing a pointer to the device list, and map
18 // name, a preferred slot #, and a key/button map. All the heavy lifting (incl.
19 // autoconnection of devices to profiles to slots) is done here.
21 // Basically, how this works is that we connect the device the user plugs into
22 // the computer to a profile in the database to a slot in the virtual Jaguar.
23 // Hopefully the configuration that the user gives us is sane enough for us to
24 // figure out how to do the right thing! By default, there is always a keyboard
25 // device plugged in; any other device that gets plugged in and wants to be in
26 // slot #0 can override it. This is so there is always a sane configuration if
27 // nothing is plugged in.
29 // Devices go into the database when the user plugs them in and runs VJ, and
30 // subsequently does anything to alter any of the existing profiles. Once a
31 // device has been seen, it can't be unseen!
35 #include <QtWidgets/QtWidgets>
41 //#define DEBUG_PROFILES
42 #define MAX_DEVICES 64
45 Profile profile
[MAX_PROFILES
];
46 Profile profileBackup
[MAX_PROFILES
];
47 int controller1Profile
;
48 int controller2Profile
;
53 char deviceNames
[MAX_DEVICES
][128];
54 static int numberOfProfilesSave
;
56 // This is so that new devices have something reasonable to show for default
57 uint32_t defaultMap
[21] = {
58 'S', 'X', 'Z', 'C', '-','7', '4', '1', '0', '8', '5', '2', '=', '9', '6',
59 '3', 'L', 'K', 'J', 'O', 'P'
63 // Function Prototypes
64 int ConnectProfileToDevice(int deviceNum
, int gamepadID
= -1);
65 int FindProfileForDevice(int deviceNum
, int preferred
, int * found
);
69 // These two functions are mainly to support the controller configuration GUI.
70 // Could just as easily go there as well (and be better placed there).
72 void SaveProfiles(void)
74 numberOfProfilesSave
= numberOfProfiles
;
75 memcpy(&profileBackup
, &profile
, sizeof(Profile
) * MAX_PROFILES
);
79 void RestoreProfiles(void)
81 memcpy(&profile
, &profileBackup
, sizeof(Profile
) * MAX_PROFILES
);
82 numberOfProfiles
= numberOfProfilesSave
;
86 void ReadProfiles(QSettings
* set
)
88 // Assume no profiles, until we read them
91 // There is always at least one device present, and it's the keyboard
92 // (hey, we're PC centric here ;-)
94 strcpy(deviceNames
[0], "Keyboard");
96 // Read the rest of the devices (if any)
97 numberOfDevices
+= set
->beginReadArray("devices");
99 for(int i
=1; i
<numberOfDevices
; i
++)
101 set
->setArrayIndex(i
- 1);
102 strcpy(deviceNames
[i
], set
->value("deviceName").toString().toUtf8().data());
103 #ifdef DEBUG_PROFILES
104 printf("Read device name: %s\n", deviceNames
[i
]);
109 numberOfProfiles
= set
->beginReadArray("profiles");
110 #ifdef DEBUG_PROFILES
111 printf("Number of profiles: %u\n", numberOfProfiles
);
114 for(int i
=0; i
<numberOfProfiles
; i
++)
116 set
->setArrayIndex(i
);
117 profile
[i
].device
= set
->value("deviceNum").toInt();
118 strcpy(profile
[i
].mapName
, set
->value("mapName").toString().toUtf8().data());
119 profile
[i
].preferredSlot
= set
->value("preferredSlot").toInt();
121 for(int j
=0; j
<21; j
++)
123 QString string
= QString("map%1").arg(j
);
124 profile
[i
].map
[j
] = set
->value(string
).toInt();
126 #ifdef DEBUG_PROFILES
127 printf("Profile #%u: device=%u (%s)\n", i
, profile
[i
].device
, deviceNames
[profile
[i
].device
]);
133 #ifdef DEBUG_PROFILES
134 printf("Number of profiles found: %u\n", numberOfProfiles
);
136 // Set up a reasonable default if no profiles were found
137 if (numberOfProfiles
== 0)
139 #ifdef DEBUG_PROFILES
140 printf("Setting up default profile...\n");
143 profile
[0].device
= 0; // Keyboard is always device #0
144 strcpy(profile
[0].mapName
, "Default");
145 profile
[0].preferredSlot
= CONTROLLER1
;
147 for(int i
=0; i
<21; i
++)
148 profile
[0].map
[i
] = defaultMap
[i
];
151 WriteLog("Read profiles = Done\n");
155 void WriteProfiles(QSettings
* set
)
158 // Don't write anything for now...
161 // NB: Should only do this if something changed; otherwise, no need to do
163 set
->beginWriteArray("devices");
165 for(int i
=1; i
<numberOfDevices
; i
++)
167 set
->setArrayIndex(i
- 1);
168 set
->setValue("deviceName", deviceNames
[i
]);
172 set
->beginWriteArray("profiles");
174 for(int i
=0; i
<numberOfProfiles
; i
++)
176 set
->setArrayIndex(i
);
177 set
->setValue("deviceNum", profile
[i
].device
);
178 set
->setValue("mapName", profile
[i
].mapName
);
179 set
->setValue("preferredSlot", profile
[i
].preferredSlot
);
181 for(int j
=0; j
<21; j
++)
183 QString string
= QString("map%1").arg(j
);
184 set
->setValue(string
, profile
[i
].map
[j
]);
192 int GetFreeProfile(void)
194 // Check for too many, return -1 if so
195 if (numberOfProfiles
== MAX_PROFILES
)
198 int profileNum
= numberOfProfiles
;
204 void DeleteProfile(int profileToDelete
)
207 if (profileToDelete
>= numberOfProfiles
)
210 // Trivial case: Profile at end of the array
211 if (profileToDelete
== (numberOfProfiles
- 1))
217 // memmove(dest, src, bytesToMove);
218 memmove(&profile
[profileToDelete
], &profile
[profileToDelete
+ 1], ((numberOfProfiles
- 1) - profileToDelete
) * sizeof(Profile
));
223 int FindDeviceNumberForName(const char * name
)
225 for(int i
=0; i
<numberOfDevices
; i
++)
227 if (strcmp(deviceNames
[i
], name
) == 0)
228 #ifdef DEBUG_PROFILES
230 printf("PROFILE: Found device #%i for name (%s)...\n", i
, name
);
233 #ifdef DEBUG_PROFILES
238 if (numberOfDevices
== MAX_DEVICES
)
241 #ifdef DEBUG_PROFILES
242 printf("Device '%s' not found, creating device...\n", name
);
244 // If the device wasn't found, it must be new; so add it to the list.
245 int deviceNum
= numberOfDevices
;
246 deviceNames
[deviceNum
][127] = 0;
247 strncpy(deviceNames
[deviceNum
], name
, 127);
254 int FindMappingsForDevice(int deviceNum
, QComboBox
* combo
)
258 for(int i
=0; i
<numberOfProfiles
; i
++)
260 //This should *never* be the case--all profiles in list are *good*
261 // if (profile[i].device == -1)
264 if (profile
[i
].device
== deviceNum
)
266 combo
->addItem(profile
[i
].mapName
, i
);
271 // If no mappings were found, create a default one for it
274 profile
[numberOfProfiles
].device
= deviceNum
;
275 strcpy(profile
[numberOfProfiles
].mapName
, "Default");
276 profile
[numberOfProfiles
].preferredSlot
= CONTROLLER1
;
278 for(int i
=0; i
<21; i
++)
279 profile
[numberOfProfiles
].map
[i
] = defaultMap
[i
];
281 combo
->addItem(profile
[numberOfProfiles
].mapName
, numberOfProfiles
);
291 int FindUsableProfiles(QComboBox
* combo
)
295 // Check for device #0 (keyboard) profiles first
296 for(int j
=0; j
<numberOfProfiles
; j
++)
298 // Check for device *and* usable configuration
299 if ((profile
[j
].device
== 0) && (profile
[j
].preferredSlot
))
301 combo
->addItem(QString("Keyboard::%1").arg(profile
[j
].mapName
), j
);
306 // Check for connected host devices next
307 for(int i
=0; i
<Gamepad::numJoysticks
; i
++)
309 int deviceNum
= FindDeviceNumberForName(Gamepad::GetJoystickName(i
));
311 for(int j
=0; j
<numberOfProfiles
; j
++)
313 if ((profile
[j
].device
== deviceNum
) && (profile
[j
].preferredSlot
))
315 combo
->addItem(QString("%1::%2").arg(Gamepad::GetJoystickName(i
)).arg(profile
[j
].mapName
), j
);
325 bool ConnectProfileToController(int profileNum
, int controllerNum
)
331 if (profile
[profileNum
].device
== -1)
334 if (controllerNum
< 0 || controllerNum
> 2)
337 uint32_t * dest
= (controllerNum
== 0 ? &vjs
.p1KeyBindings
[0] : &vjs
.p2KeyBindings
[0]);
339 for(int i
=0; i
<21; i
++)
340 dest
[i
] = profile
[profileNum
].map
[i
];
342 WriteLog("PROFILE: Successfully mapped device '%s' (%s) to controller #%u...\n", deviceNames
[profile
[profileNum
].device
], profile
[profileNum
].mapName
, controllerNum
);
348 One more stab at this...
350 - Connect keyboard to slot #0.
351 - Loop thru all connected devices. For each device:
352 - Grab all profiles for the device. For each profile:
353 - Check to see what its preferred device is.
354 - If PD is slot #0, see if slot is already taken (gamepadIDSlot1 != -1).
355 If not taken, take it; otherwise put in list to tell user to solve the
357 - If the slot is already taken and *it's the same device* as the one
358 we're looking at, set it in slot #1.
359 - If PD is slot #1, see if slot is already taken. If not, take it;
360 otherwise, put in list to tell user to solve conflict for us.
361 - If PD is slot #0 & #1, see if either is already taken. Try #0 first,
362 then try #1. If both are already taken, skip it. Do this *after* we've
363 connected devices with preferred slots.
365 void AutoConnectProfiles(void)
367 // int foundProfiles[MAX_PROFILES];
368 controller1Profile
= -1;
369 controller2Profile
= -1;
373 // Connect the keyboard automagically only if no gamepads are plugged in.
374 // Otherwise, check after all other devices have been checked, then try to
376 if (Gamepad::numJoysticks
== 0)
378 #ifdef DEBUG_PROFILES
379 printf("AutoConnect: Setting up keyboard...\n");
381 //NO! ConnectProfileToDevice(0);
383 #pragma message("Warning: !!! Need to set up scanning for multiple keyboard profiles !!!")
385 #warning "!!! Need to set up scanning for multiple keyboard profiles !!!"
387 ConnectProfileToController(0, 0);
391 // Connect the profiles that prefer a slot, if any.
392 // N.B.: Conflicts are detected, but ignored. 1st controller to grab a
393 // preferred slot gets it. :-P
394 for(int i
=0; i
<Gamepad::numJoysticks
; i
++)
396 int deviceNum
= FindDeviceNumberForName(Gamepad::GetJoystickName(i
));
397 // bool p1Overwriteable =
398 #ifdef DEBUG_PROFILES
399 printf("AutoConnect: Attempting to set up profile for device '%s' (%i)\n", Gamepad::GetJoystickName(i
), deviceNum
);
402 for(int j
=0; j
<numberOfProfiles
; j
++)
404 if (deviceNum
!= profile
[j
].device
)
407 int slot
= profile
[j
].preferredSlot
;
409 if (slot
== CONTROLLER1
)
411 if (gamepadIDSlot1
== -1)
412 controller1Profile
= j
, gamepadIDSlot1
= i
;
415 // Autoresolve simple conflict: two controllers sharing one
416 // profile mapped to slot #0.
417 if ((deviceNum
== profile
[controller1Profile
].device
) && (controller2Profile
== -1))
418 controller2Profile
= j
, gamepadIDSlot2
= i
;
420 ; // Alert user to conflict and ask to resolve
423 else if (slot
== CONTROLLER2
)
425 if (gamepadIDSlot2
== -1)
426 controller2Profile
= j
, gamepadIDSlot2
= i
;
429 // Autoresolve simple conflict: two controllers sharing one
430 // profile mapped to slot #1.
431 if ((deviceNum
== profile
[controller2Profile
].device
) && (controller1Profile
== -1))
432 controller1Profile
= j
, gamepadIDSlot1
= i
;
434 ; // Alert user to conflict and ask to resolve
440 // Connect the "don't care" states, if any. We don't roll it into the above,
441 // because it can override the profiles that have a definite preference.
442 // These should be lowest priority.
443 for(int i
=0; i
<Gamepad::numJoysticks
; i
++)
445 int deviceNum
= FindDeviceNumberForName(Gamepad::GetJoystickName(i
));
447 for(int j
=0; j
<numberOfProfiles
; j
++)
449 if (deviceNum
!= profile
[j
].device
)
452 int slot
= profile
[j
].preferredSlot
;
454 if (slot
== (CONTROLLER1
| CONTROLLER2
))
456 if (gamepadIDSlot1
== -1)
457 controller1Profile
= j
, gamepadIDSlot1
= i
;
458 else if (gamepadIDSlot2
== -1)
459 controller2Profile
= j
, gamepadIDSlot2
= i
;
464 // Connect the keyboard device (lowest priority)
465 // N.B.: The keyboard is always mapped to profile #0, so we can locate it
467 int slot
= profile
[0].preferredSlot
;
468 #ifdef DEBUG_PROFILES
469 printf("AutoConnect: Attempting to connect keyboard... (gamepadIDSlot1/2 = %i/%i)\n", gamepadIDSlot1
, gamepadIDSlot2
);
472 if ((slot
== CONTROLLER1
) && (gamepadIDSlot1
== -1))
473 controller1Profile
= 0;
474 else if ((slot
== CONTROLLER2
) && (gamepadIDSlot2
== -1))
475 controller2Profile
= 0;
476 else if (slot
== (CONTROLLER1
| CONTROLLER2
))
478 if (gamepadIDSlot1
== -1)
479 controller1Profile
= 0;
480 else if (gamepadIDSlot2
== -1)
481 controller2Profile
= 0;
484 #ifdef DEBUG_PROFILES
485 printf("AutoConnect: Profiles found: [%i, %i]\n", controller1Profile
, controller2Profile
);
487 // Finally, attempt to connect profiles to controllers
488 ConnectProfileToController(controller1Profile
, 0);
489 ConnectProfileToController(controller2Profile
, 1);
494 int ConnectProfileToDevice(int deviceNum
, int gamepadID
/*= -1*/)
496 // bool found1 = false;
497 // bool found2 = false;
498 int numberFoundForController1
= 0;
499 int numberFoundForController2
= 0;
501 for(int i
=0; i
<numberOfProfiles
; i
++)
503 // Skip profile if it's not our device
504 if (profile
[i
].device
!= deviceNum
)
507 if (profile
[i
].preferredSlot
& CONTROLLER1
)
509 controller1Profile
= i
;
510 gamepadIDSlot1
= gamepadID
;
512 numberFoundForController1
++;
515 if (profile
[i
].preferredSlot
& CONTROLLER2
)
517 controller2Profile
= i
;
518 gamepadIDSlot2
= gamepadID
;
520 numberFoundForController2
++;
525 return numberFoundForController1
+ numberFoundForController2
;
530 int FindProfileForDevice(int deviceNum
, int preferred
, int * found
)
534 for(int i
=0; i
<numberOfProfiles
; i
++)
536 // Return the profile only if it matches the passed in device and
537 // matches the passed in preference...
538 if ((profile
[i
].device
== deviceNum
) && (profile
[i
].preferredSlot
== preferred
))
539 found
[numFound
++] = i
;
547 // Also note that we have the intersection of three things here: One the one
548 // hand, we have the detected joysticks with their IDs (typically in the range
549 // of 0-7), we have our gamepad profiles and their IDs (typically can have up to
550 // 64 of them), and we have our gamepad slots that the detected joysticks can be
553 // So, when the user plugs in a gamepad, it gets a joystick ID, then the profile
554 // manager checks to see if a profile (or profiles) for it exists. If so, then
555 // it assigns that joystick ID to a gamepad slot, based upon what the user
556 // requested for that profile.
558 // A problem (perhaps) arises when you have more than one profile for a certain
559 // device, how do you know which one to use? Perhaps you have a field in the
560 // profile saying that you use this profile 1st, that one 2nd, and so on...
562 // Some use cases, and how to resolve them:
564 // - User has two of the same device, and plugs them both in. There is only one
565 // profile. In this case, the sane thing to do is ignore the "preferred slot"
566 // of the dialog and use the same profile for both controllers, and plug them
567 // both into slot #0 and #1.
568 // - User has one device, and plugs it in. There are two profiles. In this case,
569 // the profile chosen should be based upon the "preferred slot", with slot #0
570 // being the winner. If both profiles are set for slot #0, ask the user which
571 // profile to use, and set a flag in the profile to say that it is a preferred
572 // profile for that device.
573 // - In any case where there are conflicts, the user must be consulted and sane