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