comparison bindings.c @ 2053:3414a4423de1 segacd

Merge from default
author Michael Pavone <pavone@retrodev.com>
date Sat, 15 Jan 2022 13:15:21 -0800
parents 81df9aa2de9b
children 372625dd9590
comparison
equal deleted inserted replaced
1692:5dacaef602a7 2053:3414a4423de1
1 #include <string.h> 1 #include <string.h>
2 #include <stdlib.h>
2 #include "render.h" 3 #include "render.h"
3 #include "system.h" 4 #include "system.h"
4 #include "io.h" 5 #include "io.h"
5 #include "blastem.h" 6 #include "blastem.h"
6 #include "saves.h" 7 #include "saves.h"
33 UI_TOGGLE_FULLSCREEN, 34 UI_TOGGLE_FULLSCREEN,
34 UI_SOFT_RESET, 35 UI_SOFT_RESET,
35 UI_RELOAD, 36 UI_RELOAD,
36 UI_SMS_PAUSE, 37 UI_SMS_PAUSE,
37 UI_SCREENSHOT, 38 UI_SCREENSHOT,
39 UI_VGM_LOG,
38 UI_EXIT, 40 UI_EXIT,
39 UI_PLANE_DEBUG, 41 UI_PLANE_DEBUG,
40 UI_VRAM_DEBUG, 42 UI_VRAM_DEBUG,
41 UI_CRAM_DEBUG, 43 UI_CRAM_DEBUG,
42 UI_COMPOSITE_DEBUG 44 UI_COMPOSITE_DEBUG
255 257
256 #ifdef _WIN32 258 #ifdef _WIN32
257 #define localtime_r(a,b) localtime(a) 259 #define localtime_r(a,b) localtime(a)
258 #endif 260 #endif
259 261
262 char *get_content_config_path(char *config_path, char *config_template, char *default_name)
263 {
264 char *base = tern_find_path(config, config_path, TVAL_PTR).ptrval;
265 if (!base) {
266 base = "$HOME";
267 }
268 const system_media *media = current_media();
269 tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir());
270 vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
271 vars = tern_insert_ptr(vars, "USERDATA", (char *)get_userdata_dir());
272 vars = tern_insert_ptr(vars, "ROMNAME", media->name);
273 vars = tern_insert_ptr(vars, "ROMDIR", media->dir);
274 base = replace_vars(base, vars, 1);
275 tern_free(vars);
276 ensure_dir_exists(base);
277 time_t now = time(NULL);
278 struct tm local_store;
279 char fname_part[256];
280 char *template = tern_find_path(config, config_template, TVAL_PTR).ptrval;
281 if (template) {
282 vars = tern_insert_ptr(NULL, "ROMNAME", media->name);
283 template = replace_vars(template, vars, 0);
284 } else {
285 template = strdup(default_name);
286 }
287 strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store));
288 char const *parts[] = {base, PATH_SEP, fname_part};
289 char *path = alloc_concat_m(3, parts);
290 free(base);
291 free(template);
292 return path;
293 }
294
260 void handle_binding_up(keybinding * binding) 295 void handle_binding_up(keybinding * binding)
261 { 296 {
297 uint8_t allow_content_binds = content_binds_enabled && current_system;
262 switch(binding->bind_type) 298 switch(binding->bind_type)
263 { 299 {
264 case BIND_GAMEPAD: 300 case BIND_GAMEPAD:
265 if (content_binds_enabled && current_system->gamepad_up) { 301 if (allow_content_binds && current_system->gamepad_up) {
266 current_system->gamepad_up(current_system, binding->subtype_a, binding->subtype_b); 302 current_system->gamepad_up(current_system, binding->subtype_a, binding->subtype_b);
267 } 303 }
268 break; 304 break;
269 case BIND_MOUSE: 305 case BIND_MOUSE:
270 if (content_binds_enabled && current_system->mouse_up) { 306 if (allow_content_binds && current_system->mouse_up) {
271 current_system->mouse_up(current_system, binding->subtype_a, binding->subtype_b); 307 current_system->mouse_up(current_system, binding->subtype_a, binding->subtype_b);
272 } 308 }
273 break; 309 break;
274 case BIND_UI: 310 case BIND_UI:
275 switch (binding->subtype_a) 311 switch (binding->subtype_a)
276 { 312 {
277 case UI_DEBUG_MODE_INC: 313 case UI_DEBUG_MODE_INC:
278 if (content_binds_enabled) { 314 if (allow_content_binds) {
279 current_system->inc_debug_mode(current_system); 315 current_system->inc_debug_mode(current_system);
280 } 316 }
281 break; 317 break;
282 case UI_ENTER_DEBUGGER: 318 case UI_ENTER_DEBUGGER:
283 if (content_binds_enabled) { 319 if (allow_content_binds) {
284 current_system->enter_debugger = 1; 320 current_system->enter_debugger = 1;
285 } 321 }
286 break; 322 break;
287 case UI_SAVE_STATE: 323 case UI_SAVE_STATE:
288 if (content_binds_enabled) { 324 if (allow_content_binds) {
289 current_system->save_state = QUICK_SAVE_SLOT+1; 325 current_system->save_state = QUICK_SAVE_SLOT+1;
290 } 326 }
291 break; 327 break;
292 case UI_NEXT_SPEED: 328 case UI_NEXT_SPEED:
293 if (content_binds_enabled) { 329 if (allow_content_binds) {
294 current_speed++; 330 current_speed++;
295 if (current_speed >= num_speeds) { 331 if (current_speed >= num_speeds) {
296 current_speed = 0; 332 current_speed = 0;
297 } 333 }
298 printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]); 334 printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
299 current_system->set_speed_percent(current_system, speeds[current_speed]); 335 current_system->set_speed_percent(current_system, speeds[current_speed]);
300 } 336 }
301 break; 337 break;
302 case UI_PREV_SPEED: 338 case UI_PREV_SPEED:
303 if (content_binds_enabled) { 339 if (allow_content_binds) {
304 current_speed--; 340 current_speed--;
305 if (current_speed < 0) { 341 if (current_speed < 0) {
306 current_speed = num_speeds - 1; 342 current_speed = num_speeds - 1;
307 } 343 }
308 printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]); 344 printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
309 current_system->set_speed_percent(current_system, speeds[current_speed]); 345 current_system->set_speed_percent(current_system, speeds[current_speed]);
310 } 346 }
311 break; 347 break;
312 case UI_SET_SPEED: 348 case UI_SET_SPEED:
313 if (content_binds_enabled) { 349 if (allow_content_binds) {
314 if (binding->subtype_b < num_speeds) { 350 if (binding->subtype_b < num_speeds) {
315 current_speed = binding->subtype_b; 351 current_speed = binding->subtype_b;
316 printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]); 352 printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
317 current_system->set_speed_percent(current_system, speeds[current_speed]); 353 current_system->set_speed_percent(current_system, speeds[current_speed]);
318 } else { 354 } else {
326 mouse_captured = 0; 362 mouse_captured = 0;
327 render_relative_mouse(0); 363 render_relative_mouse(0);
328 } 364 }
329 break; 365 break;
330 case UI_TOGGLE_KEYBOARD_CAPTURE: 366 case UI_TOGGLE_KEYBOARD_CAPTURE:
331 if (content_binds_enabled && current_system->has_keyboard) { 367 if (allow_content_binds && current_system->has_keyboard) {
332 keyboard_captured = !keyboard_captured; 368 keyboard_captured = !keyboard_captured;
333 } 369 }
334 break; 370 break;
335 case UI_TOGGLE_FULLSCREEN: 371 case UI_TOGGLE_FULLSCREEN:
336 render_toggle_fullscreen(); 372 render_toggle_fullscreen();
337 break; 373 break;
338 case UI_SOFT_RESET: 374 case UI_SOFT_RESET:
339 if (content_binds_enabled) { 375 if (allow_content_binds) {
340 current_system->soft_reset(current_system); 376 current_system->soft_reset(current_system);
341 } 377 }
342 break; 378 break;
343 case UI_RELOAD: 379 case UI_RELOAD:
344 if (content_binds_enabled) { 380 if (allow_content_binds) {
345 reload_media(); 381 reload_media();
346 } 382 }
347 break; 383 break;
348 case UI_SMS_PAUSE: 384 case UI_SMS_PAUSE:
349 if (content_binds_enabled && current_system->gamepad_down) { 385 if (allow_content_binds && current_system->gamepad_down) {
350 current_system->gamepad_down(current_system, GAMEPAD_MAIN_UNIT, MAIN_UNIT_PAUSE); 386 current_system->gamepad_down(current_system, GAMEPAD_MAIN_UNIT, MAIN_UNIT_PAUSE);
351 } 387 }
352 break; 388 break;
353 case UI_SCREENSHOT: { 389 case UI_SCREENSHOT:
354 if (content_binds_enabled) { 390 if (allow_content_binds) {
355 char *screenshot_base = tern_find_path(config, "ui\0screenshot_path\0", TVAL_PTR).ptrval; 391 char *path = get_content_config_path("ui\0screenshot_path\0", "ui\0screenshot_template\0", "blastem_%c.ppm");
356 if (!screenshot_base) { 392 render_save_screenshot(path);
357 screenshot_base = "$HOME"; 393 }
394 break;
395 case UI_VGM_LOG:
396 if (allow_content_binds && current_system->start_vgm_log) {
397 if (current_system->vgm_logging) {
398 current_system->stop_vgm_log(current_system);
399 } else {
400 char *path = get_content_config_path("ui\0vgm_path\0", "ui\0vgm_template\0", "blastem_%c.vgm");
401 current_system->start_vgm_log(current_system, path);
402 free(path);
358 } 403 }
359 tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir()); 404 }
360 vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir()); 405 break;
361 screenshot_base = replace_vars(screenshot_base, vars, 1);
362 tern_free(vars);
363 time_t now = time(NULL);
364 struct tm local_store;
365 char fname_part[256];
366 char *template = tern_find_path(config, "ui\0screenshot_template\0", TVAL_PTR).ptrval;
367 if (!template) {
368 template = "blastem_%c.ppm";
369 }
370 strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store));
371 char const *parts[] = {screenshot_base, PATH_SEP, fname_part};
372 char *path = alloc_concat_m(3, parts);
373 free(screenshot_base);
374 render_save_screenshot(path);
375 }
376 break;
377 }
378 case UI_EXIT: 406 case UI_EXIT:
379 #ifndef DISABLE_NUKLEAR 407 #ifndef DISABLE_NUKLEAR
380 if (is_nuklear_active()) { 408 if (is_nuklear_active()) {
381 show_pause_menu(); 409 show_pause_menu();
382 } else { 410 } else {
383 #endif 411 #endif
384 current_system->request_exit(current_system); 412 system_request_exit(current_system, 1);
385 if (current_system->type == SYSTEM_GENESIS) { 413 if (current_system->type == SYSTEM_GENESIS) {
386 genesis_context *gen = (genesis_context *)current_system; 414 genesis_context *gen = (genesis_context *)current_system;
387 if (gen->extra) { 415 if (gen->extra) {
388 //TODO: More robust mechanism for detecting menu 416 //TODO: More robust mechanism for detecting menu
389 menu_context *menu = gen->extra; 417 menu_context *menu = gen->extra;
396 break; 424 break;
397 case UI_PLANE_DEBUG: 425 case UI_PLANE_DEBUG:
398 case UI_VRAM_DEBUG: 426 case UI_VRAM_DEBUG:
399 case UI_CRAM_DEBUG: 427 case UI_CRAM_DEBUG:
400 case UI_COMPOSITE_DEBUG: 428 case UI_COMPOSITE_DEBUG:
401 if (content_binds_enabled) { 429 if (allow_content_binds) {
402 vdp_context *vdp = NULL; 430 vdp_context *vdp = NULL;
403 if (current_system->type == SYSTEM_GENESIS) { 431 if (current_system->type == SYSTEM_GENESIS) {
404 genesis_context *gen = (genesis_context *)current_system; 432 genesis_context *gen = (genesis_context *)current_system;
405 vdp = gen->vdp; 433 vdp = gen->vdp;
406 } else if (current_system->type == SYSTEM_SMS) { 434 } else if (current_system->type == SYSTEM_SMS) {
554 582
555 int parse_binding_target(int device_num, char * target, tern_node * padbuttons, tern_node *mousebuttons, uint8_t * subtype_a, uint8_t * subtype_b) 583 int parse_binding_target(int device_num, char * target, tern_node * padbuttons, tern_node *mousebuttons, uint8_t * subtype_a, uint8_t * subtype_b)
556 { 584 {
557 const int gpadslen = strlen("gamepads."); 585 const int gpadslen = strlen("gamepads.");
558 const int mouselen = strlen("mouse."); 586 const int mouselen = strlen("mouse.");
559 if (!strncmp(target, "gamepads.", gpadslen)) { 587 if (startswith(target, "gamepads.")) {
560 int padnum = target[gpadslen] == 'n' ? device_num + 1 : target[gpadslen] - '0'; 588 int padnum = target[gpadslen] == 'n' ? device_num + 1 : target[gpadslen] - '0';
561 if (padnum >= 1 && padnum <= 8) { 589 if (padnum >= 1 && padnum <= 8) {
562 int button = tern_find_int(padbuttons, target + gpadslen + 1, 0); 590 int button = tern_find_int(padbuttons, target + gpadslen + 1, 0);
563 if (button) { 591 if (button) {
564 *subtype_a = padnum; 592 *subtype_a = padnum;
572 } 600 }
573 } 601 }
574 } else { 602 } else {
575 warning("Gamepad mapping string '%s' refers to an invalid gamepad number %c\n", target, target[gpadslen]); 603 warning("Gamepad mapping string '%s' refers to an invalid gamepad number %c\n", target, target[gpadslen]);
576 } 604 }
577 } else if(!strncmp(target, "mouse.", mouselen)) { 605 } else if(startswith(target, "mouse.")) {
578 int mousenum = target[mouselen] == 'n' ? device_num + 1 : target[mouselen] - '0'; 606 int mousenum = target[mouselen] == 'n' ? device_num + 1 : target[mouselen] - '0';
579 if (mousenum >= 1 && mousenum <= 8) { 607 if (mousenum >= 1 && mousenum <= 8) {
580 int button = tern_find_int(mousebuttons, target + mouselen + 1, 0); 608 int button = tern_find_int(mousebuttons, target + mouselen + 1, 0);
581 if (button) { 609 if (button) {
582 *subtype_a = mousenum; 610 *subtype_a = mousenum;
590 } 618 }
591 } 619 }
592 } else { 620 } else {
593 warning("Gamepad mapping string '%s' refers to an invalid mouse number %c\n", target, target[mouselen]); 621 warning("Gamepad mapping string '%s' refers to an invalid mouse number %c\n", target, target[mouselen]);
594 } 622 }
595 } else if(!strncmp(target, "ui.", strlen("ui."))) { 623 } else if(startswith(target, "ui.")) {
596 if (!strcmp(target + 3, "vdp_debug_mode")) { 624 if (!strcmp(target + 3, "vdp_debug_mode")) {
597 *subtype_a = UI_DEBUG_MODE_INC; 625 *subtype_a = UI_DEBUG_MODE_INC;
598 } else if(!strcmp(target + 3, "vdp_debug_pal")) { 626 } else if(!strcmp(target + 3, "vdp_debug_pal")) {
599 //legacy binding, ignore 627 //legacy binding, ignore
600 return 0; 628 return 0;
601 } else if(!strcmp(target + 3, "enter_debugger")) { 629 } else if(!strcmp(target + 3, "enter_debugger")) {
602 *subtype_a = UI_ENTER_DEBUGGER; 630 *subtype_a = UI_ENTER_DEBUGGER;
603 } else if(!strcmp(target + 3, "save_state")) { 631 } else if(!strcmp(target + 3, "save_state")) {
604 *subtype_a = UI_SAVE_STATE; 632 *subtype_a = UI_SAVE_STATE;
605 } else if(!strncmp(target + 3, "set_speed.", strlen("set_speed."))) { 633 } else if(startswith(target + 3, "set_speed.")) {
606 *subtype_a = UI_SET_SPEED; 634 *subtype_a = UI_SET_SPEED;
607 *subtype_b = atoi(target + 3 + strlen("set_speed.")); 635 *subtype_b = atoi(target + 3 + strlen("set_speed."));
608 } else if(!strcmp(target + 3, "next_speed")) { 636 } else if(!strcmp(target + 3, "next_speed")) {
609 *subtype_a = UI_NEXT_SPEED; 637 *subtype_a = UI_NEXT_SPEED;
610 } else if(!strcmp(target + 3, "prev_speed")) { 638 } else if(!strcmp(target + 3, "prev_speed")) {
621 *subtype_a = UI_RELOAD; 649 *subtype_a = UI_RELOAD;
622 } else if (!strcmp(target + 3, "sms_pause")) { 650 } else if (!strcmp(target + 3, "sms_pause")) {
623 *subtype_a = UI_SMS_PAUSE; 651 *subtype_a = UI_SMS_PAUSE;
624 } else if (!strcmp(target + 3, "screenshot")) { 652 } else if (!strcmp(target + 3, "screenshot")) {
625 *subtype_a = UI_SCREENSHOT; 653 *subtype_a = UI_SCREENSHOT;
654 } else if (!strcmp(target + 3, "vgm_log")) {
655 *subtype_a = UI_VGM_LOG;
626 } else if(!strcmp(target + 3, "exit")) { 656 } else if(!strcmp(target + 3, "exit")) {
627 *subtype_a = UI_EXIT; 657 *subtype_a = UI_EXIT;
628 } else if (!strcmp(target + 3, "plane_debug")) { 658 } else if (!strcmp(target + 3, "plane_debug")) {
629 *subtype_a = UI_PLANE_DEBUG; 659 *subtype_a = UI_PLANE_DEBUG;
630 } else if (!strcmp(target + 3, "vram_debug")) { 660 } else if (!strcmp(target + 3, "vram_debug")) {
812 hostbutton = render_translate_input_name(hostpadnum, key, 0); 842 hostbutton = render_translate_input_name(hostpadnum, key, 0);
813 if (hostbutton < 0) { 843 if (hostbutton < 0) {
814 if (hostbutton == RENDER_INVALID_NAME) { 844 if (hostbutton == RENDER_INVALID_NAME) {
815 warning("%s is not a valid gamepad input name\n", key); 845 warning("%s is not a valid gamepad input name\n", key);
816 } else if (hostbutton == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) { 846 } else if (hostbutton == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) {
817 warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); 847 debug_message("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
818 map_warning_pad = hostpadnum; 848 map_warning_pad = hostpadnum;
819 } 849 }
820 return; 850 return;
821 } 851 }
822 if (hostbutton & RENDER_DPAD_BIT) { 852 if (hostbutton & RENDER_DPAD_BIT) {
823 bind_dpad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), bindtype, subtype_a, subtype_b); 853 bind_dpad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), bindtype, subtype_a, subtype_b);
824 return; 854 return;
825 } else if (hostbutton & RENDER_AXIS_BIT) { 855 } else if (hostbutton & RENDER_AXIS_BIT) {
826 bind_axis(hostpadnum, render_axis_part(hostbutton), 1, bindtype, subtype_a, subtype_b); 856 bind_axis(hostpadnum, render_axis_part(hostbutton), hostbutton & RENDER_AXIS_POS, bindtype, subtype_a, subtype_b);
827 return; 857 return;
828 } 858 }
829 } 859 }
830 bind_button(hostpadnum, hostbutton, bindtype, subtype_a, subtype_b); 860 bind_button(hostpadnum, hostbutton, bindtype, subtype_a, subtype_b);
831 } 861 }
859 axis = render_translate_input_name(hostpadnum, key, 1); 889 axis = render_translate_input_name(hostpadnum, key, 1);
860 if (axis < 0) { 890 if (axis < 0) {
861 if (axis == RENDER_INVALID_NAME) { 891 if (axis == RENDER_INVALID_NAME) {
862 warning("%s is not a valid gamepad input name\n", key); 892 warning("%s is not a valid gamepad input name\n", key);
863 } else if (axis == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) { 893 } else if (axis == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) {
864 warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); 894 debug_message("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
865 map_warning_pad = hostpadnum; 895 map_warning_pad = hostpadnum;
866 } 896 }
867 goto done; 897 goto done;
868 } 898 }
869 if (axis & RENDER_DPAD_BIT) { 899 if (axis & RENDER_DPAD_BIT) {
955 for (int dpad = 0; dpad < 10; dpad++) 985 for (int dpad = 0; dpad < 10; dpad++)
956 { 986 {
957 char numstr[2] = {dpad + '0', 0}; 987 char numstr[2] = {dpad + '0', 0};
958 tern_node * pad_dpad = tern_find_node(dpad_node, numstr); 988 tern_node * pad_dpad = tern_find_node(dpad_node, numstr);
959 char * dirs[] = {"up", "down", "left", "right"}; 989 char * dirs[] = {"up", "down", "left", "right"};
960 //TODO: Support controllers that have d-pads implemented as analog axes or buttons 990 char *render_dirs[] = {"dpup", "dpdown", "dpleft", "dpright"};
961 int dirnums[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT}; 991 int dirnums[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
962 for (int dir = 0; dir < sizeof(dirs)/sizeof(dirs[0]); dir++) { 992 for (int dir = 0; dir < sizeof(dirs)/sizeof(dirs[0]); dir++) {
963 char * target = tern_find_ptr(pad_dpad, dirs[dir]); 993 char * target = tern_find_ptr(pad_dpad, dirs[dir]);
964 if (target) { 994 if (target) {
965 uint8_t subtype_a = 0, subtype_b = 0; 995 uint8_t subtype_a = 0, subtype_b = 0;
966 int bindtype = parse_binding_target(joystick, target, get_pad_buttons(), get_mouse_buttons(), &subtype_a, &subtype_b); 996 int bindtype = parse_binding_target(joystick, target, get_pad_buttons(), get_mouse_buttons(), &subtype_a, &subtype_b);
967 bind_dpad(joystick, dpad, dirnums[dir], bindtype, subtype_a, subtype_b); 997 int32_t hostbutton = dpad >0 ? -1 : render_translate_input_name(joystick, render_dirs[dir], 0);
998 if (hostbutton < 0) {
999 //assume this is a raw dpad mapping
1000 bind_dpad(joystick, dpad, dirnums[dir], bindtype, subtype_a, subtype_b);
1001 } else if (hostbutton & RENDER_DPAD_BIT) {
1002 bind_dpad(joystick, render_dpad_part(hostbutton), render_direction_part(hostbutton), bindtype, subtype_a, subtype_b);
1003 } else if (hostbutton & RENDER_AXIS_BIT) {
1004 //SDL2 knows internally whether this should be a positive or negative binding, but doesn't expose that externally
1005 //for now I'll just assume that any controller with axes for a d-pad has these mapped the "sane" way
1006 bind_axis(joystick, render_axis_part(hostbutton), dir == 1 || dir == 3 ? 1 : 0, bindtype, subtype_a, subtype_b);
1007 } else {
1008 bind_button(joystick, hostbutton, bindtype, subtype_a, subtype_b);
1009 }
968 } 1010 }
969 } 1011 }
970 } 1012 }
971 } 1013 }
972 tern_node *button_node = tern_find_node(pad, "buttons"); 1014 tern_node *button_node = tern_find_node(pad, "buttons");