/*****************************************************************************\ Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. This file is licensed under the Snes9x License. For further information, consult the LICENSE file in the root directory. \*****************************************************************************/ #include #include "SDL_joystick.h" #include "gtk_s9x.h" #include "gtk_config.h" #include "gtk_control.h" #include "gtk_file.h" #include "snes9x.h" #include "controls.h" #include "display.h" #include "gfx.h" const BindingLink b_links[] = { /* Joypad-specific bindings. "Joypad# " will be prepended */ { "b_up", "Up" }, { "b_down", "Down" }, { "b_left", "Left" }, { "b_right", "Right" }, { "b_start", "Start" }, { "b_select", "Select" }, { "b_a", "A" }, { "b_b", "B" }, { "b_x", "X" }, { "b_y", "Y" }, { "b_l", "L" }, { "b_r", "R" }, { "b_a_turbo", "Turbo A" }, { "b_b_turbo", "Turbo B" }, { "b_x_turbo", "Turbo X" }, { "b_y_turbo", "Turbo Y" }, { "b_l_turbo", "Turbo L" }, { "b_r_turbo", "Turbo R" }, { "b_a_sticky", "Sticky A" }, { "b_b_sticky", "Sticky B" }, { "b_x_sticky", "Sticky X" }, { "b_y_sticky", "Sticky Y" }, { "b_l_sticky", "Sticky L" }, { "b_r_sticky", "Sticky R" }, /* Emulator based bindings */ { "b_open_rom", "GTK_open_rom" }, { "b_enable_turbo", "EmuTurbo" }, { "b_toggle_turbo", "ToggleEmuTurbo" }, { "b_pause", "GTK_pause" }, { "b_decrease_frame_rate", "DecFrameRate" }, { "b_increase_frame_rate", "IncFrameRate" }, { "b_decrease_frame_time", "DecFrameTime" }, { "b_increase_frame_time", "IncFrameTime" }, { "b_hardware_reset", "Reset" }, { "b_soft_reset", "SoftReset" }, { "b_quit", "GTK_quit" }, { "b_bg_layer_0", "ToggleBG0" }, { "b_bg_layer_1", "ToggleBG1" }, { "b_bg_layer_2", "ToggleBG2" }, { "b_bg_layer_3", "ToggleBG3" }, { "b_sprites", "ToggleSprites" }, { "b_bg_layering_hack", "BGLayeringHack" }, { "b_screenshot", "Screenshot" }, { "b_fullscreen", "GTK_fullscreen" }, { "b_state_save_current", "GTK_state_save_current" }, { "b_state_load_current", "GTK_state_load_current" }, { "b_state_increment_save","GTK_state_increment_save" }, { "b_state_decrement_load","GTK_state_decrement_load" }, { "b_state_increment", "GTK_state_increment" }, { "b_state_decrement", "GTK_state_decrement" }, { "b_save_0", "QuickSave000" }, { "b_save_1", "QuickSave001" }, { "b_save_2", "QuickSave002" }, { "b_save_3", "QuickSave003" }, { "b_save_4", "QuickSave004" }, { "b_save_5", "QuickSave005" }, { "b_save_6", "QuickSave006" }, { "b_save_7", "QuickSave007" }, { "b_save_8", "QuickSave008" }, { "b_save_9", "QuickSave009" }, { "b_load_0", "QuickLoad000" }, { "b_load_1", "QuickLoad001" }, { "b_load_2", "QuickLoad002" }, { "b_load_3", "QuickLoad003" }, { "b_load_4", "QuickLoad004" }, { "b_load_5", "QuickLoad005" }, { "b_load_6", "QuickLoad006" }, { "b_load_7", "QuickLoad007" }, { "b_load_8", "QuickLoad008" }, { "b_load_9", "QuickLoad009" }, { "b_sound_channel_0", "SoundChannel0" }, { "b_sound_channel_1", "SoundChannel1" }, { "b_sound_channel_2", "SoundChannel2" }, { "b_sound_channel_3", "SoundChannel3" }, { "b_sound_channel_4", "SoundChannel4" }, { "b_sound_channel_5", "SoundChannel5" }, { "b_sound_channel_6", "SoundChannel6" }, { "b_sound_channel_7", "SoundChannel7" }, { "b_all_sound_channels", "SoundChannelsOn" }, { "b_save_spc", "GTK_save_spc" }, { "b_begin_recording_movie", "BeginRecordingMovie" }, { "b_stop_recording_movie", "EndRecordingMovie" }, { "b_load_movie", "LoadMovie" }, { "b_seek_to_frame", "GTK_seek_to_frame" }, { "b_swap_controllers", "GTK_swap_controllers" }, { "b_rewind", "GTK_rewind" }, { "b_grab_mouse", "GTK_grab_mouse" }, { NULL, NULL } }; /* Where the page breaks occur in the preferences pane */ const int b_breaks[] = { 12, /* End of main buttons */ 24, /* End of turbo/sticky buttons */ 35, /* End of base emulator buttons */ 43, /* End of Graphic options */ 69, /* End of save/load states */ 78, /* End of sound buttons */ 86, /* End of miscellaneous buttons */ -1 }; static int joystick_lock = 0; bool S9xPollButton(uint32 id, bool *pressed) { return true; } bool S9xPollAxis(uint32 id, int16 *value) { return true; } bool S9xPollPointer(uint32 id, int16 *x, int16 *y) { *x = top_level->snes_mouse_x; *y = top_level->snes_mouse_y; return true; } bool S9xIsMousePluggedIn() { enum controllers ctl; int8 id1, id2, id3, id4; for (int i = 0; i <= 1; i++) { S9xGetController(i, &ctl, &id1, &id2, &id3, &id4); if (ctl == CTL_MOUSE || ctl == CTL_SUPERSCOPE) return true; } return false; } bool S9xGrabJoysticks() { if (joystick_lock) return false; joystick_lock++; return true; } void S9xReleaseJoysticks() { joystick_lock--; } static void swap_controllers_1_2() { JoypadBinding interrim; interrim = gui_config->pad[0]; gui_config->pad[0] = gui_config->pad[1]; gui_config->pad[1] = interrim; gui_config->rebind_keys(); } static void change_slot(int difference) { static char buf[256]; gui_config->current_save_slot += difference; gui_config->current_save_slot %= 1000; if (gui_config->current_save_slot < 0) gui_config->current_save_slot += 1000; if (!gui_config->rom_loaded) return; snprintf(buf, 256, "State Slot: %d", gui_config->current_save_slot); S9xSetInfoString(buf); GFX.InfoStringTimeout = 60; } void S9xHandlePortCommand(s9xcommand_t cmd, int16 data1, int16 data2) { static bool quit_binding_down = false; if (data1 == true) { if (cmd.port[0] == PORT_QUIT) quit_binding_down = true; else if (cmd.port[0] == PORT_REWIND) Settings.Rewinding = true; } if (data1 == false) /* Release */ { if (cmd.port[0] != PORT_QUIT) { quit_binding_down = false; } if (cmd.port[0] == PORT_COMMAND_FULLSCREEN) { top_level->toggle_fullscreen_mode(); } else if (cmd.port[0] == PORT_COMMAND_SAVE_SPC) { top_level->save_spc_dialog(); } else if (cmd.port[0] == PORT_OPEN_ROM) { top_level->open_rom_dialog(); } else if (cmd.port[0] == PORT_PAUSE) { if (!(top_level->user_pause)) top_level->pause_from_user(); else top_level->unpause_from_user(); } else if (cmd.port[0] == PORT_REWIND) { Settings.Rewinding = false; } else if (cmd.port[0] == PORT_SEEK_TO_FRAME) { top_level->movie_seek_dialog(); } else if (cmd.port[0] == PORT_SWAP_CONTROLLERS) { swap_controllers_1_2(); } else if (cmd.port[0] == PORT_QUIT) { if (quit_binding_down) S9xExit(); } else if (cmd.port[0] >= PORT_QUICKLOAD0 && cmd.port[0] <= PORT_QUICKLOAD9) { S9xQuickLoadSlot(cmd.port[0] - PORT_QUICKLOAD0); } else if (cmd.port[0] == PORT_SAVESLOT) { S9xQuickSaveSlot(gui_config->current_save_slot); } else if (cmd.port[0] == PORT_LOADSLOT) { S9xQuickLoadSlot(gui_config->current_save_slot); } else if (cmd.port[0] == PORT_INCREMENTSAVESLOT) { change_slot(1); S9xQuickSaveSlot(gui_config->current_save_slot); } else if (cmd.port[0] == PORT_DECREMENTLOADSLOT) { change_slot(-1); S9xQuickLoadSlot(gui_config->current_save_slot); } else if (cmd.port[0] == PORT_INCREMENTSLOT) { change_slot(1); } else if (cmd.port[0] == PORT_DECREMENTSLOT) { change_slot(-1); } else if (cmd.port[0] == PORT_GRABMOUSE) { top_level->toggle_grab_mouse(); } } } Binding S9xGetBindingByName(const char *name) { for (int i = 0; i < NUM_EMU_LINKS; i++) { if (!strcasecmp(b_links[i + NUM_JOYPAD_LINKS].snes9x_name, name)) { return gui_config->shortcut[i]; } } return Binding(); } s9xcommand_t S9xGetPortCommandT(const char *name) { s9xcommand_t cmd; cmd.type = S9xButtonPort; cmd.multi_press = 0; cmd.button_norpt = 0; cmd.port[0] = 0; cmd.port[1] = 0; cmd.port[2] = 0; cmd.port[3] = 0; if (!strcasecmp(name, "GTK_fullscreen")) { cmd.port[0] = PORT_COMMAND_FULLSCREEN; } else if (!strcasecmp(name, "GTK_save_spc")) { cmd.port[0] = PORT_COMMAND_SAVE_SPC; } else if (!strcasecmp(name, "GTK_open_rom")) { cmd.port[0] = PORT_OPEN_ROM; } else if (!strcasecmp(name, "GTK_pause")) { cmd.port[0] = PORT_PAUSE; } else if (!strcasecmp(name, "GTK_seek_to_frame")) { cmd.port[0] = PORT_SEEK_TO_FRAME; } else if (!strcasecmp(name, "GTK_quit")) { cmd.port[0] = PORT_QUIT; } else if (!strcasecmp(name, "GTK_swap_controllers")) { cmd.port[0] = PORT_SWAP_CONTROLLERS; } else if (!strcasecmp(name, "GTK_rewind")) { cmd.port[0] = PORT_REWIND; } else if (strstr(name, "QuickLoad000")) { cmd.port[0] = PORT_QUICKLOAD0; } else if (strstr(name, "QuickLoad001")) { cmd.port[0] = PORT_QUICKLOAD1; } else if (strstr(name, "QuickLoad002")) { cmd.port[0] = PORT_QUICKLOAD2; } else if (strstr(name, "QuickLoad003")) { cmd.port[0] = PORT_QUICKLOAD3; } else if (strstr(name, "QuickLoad004")) { cmd.port[0] = PORT_QUICKLOAD4; } else if (strstr(name, "QuickLoad005")) { cmd.port[0] = PORT_QUICKLOAD5; } else if (strstr(name, "QuickLoad006")) { cmd.port[0] = PORT_QUICKLOAD6; } else if (strstr(name, "QuickLoad007")) { cmd.port[0] = PORT_QUICKLOAD7; } else if (strstr(name, "QuickLoad008")) { cmd.port[0] = PORT_QUICKLOAD8; } else if (strstr(name, "QuickLoad009")) { cmd.port[0] = PORT_QUICKLOAD9; } else if (strstr(name, "GTK_state_save_current")) { cmd.port[0] = PORT_SAVESLOT; } else if (strstr(name, "GTK_state_load_current")) { cmd.port[0] = PORT_LOADSLOT; } else if (strstr(name, "GTK_state_increment_save")) { cmd.port[0] = PORT_INCREMENTSAVESLOT; } else if (strstr(name, "GTK_state_decrement_load")) { cmd.port[0] = PORT_DECREMENTLOADSLOT; } else if (strstr(name, "GTK_state_increment")) { cmd.port[0] = PORT_INCREMENTSLOT; } else if (strstr(name, "GTK_state_decrement")) { cmd.port[0] = PORT_DECREMENTSLOT; } else if (strstr(name, "GTK_grab_mouse")) { cmd.port[0] = PORT_GRABMOUSE; } else { cmd = S9xGetCommandT(name); } return cmd; } void S9xProcessEvents(bool8 block) { JoyEvent event; Binding binding; if (S9xGrabJoysticks()) { gui_config->joysticks.poll_events(); for (auto &j : gui_config->joysticks) { while (j.second->get_event(&event)) { binding = Binding(j.second->joynum, event.parameter, 0); S9xReportButton(binding.hex(), event.state == JOY_PRESSED ? 1 : 0); gui_config->screensaver_needs_reset = true; } } S9xReleaseJoysticks(); } } void S9xInitInputDevices() { SDL_Init(SDL_INIT_JOYSTICK); size_t num_joysticks = SDL_NumJoysticks(); for (size_t i = 0; i < num_joysticks; i++) { gui_config->joysticks.add(i); } //First plug in both, they'll change later as needed S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0); S9xSetController(1, CTL_JOYPAD, 1, 0, 0, 0); } void S9xDeinitInputDevices() { gui_config->joysticks.clear(); SDL_Quit(); } JoyDevice::JoyDevice() { enabled = false; filedes = NULL; mode = JOY_MODE_INDIVIDUAL; } JoyDevice::~JoyDevice() { if (filedes) { SDL_JoystickClose(filedes); } } bool JoyDevice::set_sdl_joystick(unsigned int sdl_device_index, int new_joynum) { if ((int)sdl_device_index >= SDL_NumJoysticks()) { enabled = false; return false; } filedes = SDL_JoystickOpen(sdl_device_index); if (!filedes) return false; enabled = true; instance_id = SDL_JoystickInstanceID(filedes); joynum = new_joynum; int num_axes = SDL_JoystickNumAxes(filedes); int num_hats = SDL_JoystickNumHats(filedes); axis.resize(num_axes); hat.resize(num_hats); calibration.resize(num_axes); for (int i = 0; i < num_axes; i++) { calibration[i].min = -32767; calibration[i].max = 32767; calibration[i].center = 0; } description = SDL_JoystickName(filedes); description += ": "; description += std::to_string(SDL_JoystickNumButtons(filedes)); description += " buttons, "; description += std::to_string(num_axes); description += " axes, "; description += std::to_string(num_hats); description += " hats\n"; for (auto &i : axis) i = 0; return true; } void JoyDevice::add_event(unsigned int parameter, unsigned int state) { JoyEvent event = { parameter, state }; queue.push(event); } void JoyDevice::register_centers() { for (size_t i = 0; i < axis.size(); i++) { calibration[i].center = SDL_JoystickGetAxis(filedes, i); /* Snap centers to specific target points */ if (calibration[i].center < -24576) calibration[i].center = -32768; else if (calibration[i].center < -8192) calibration[i].center = -16384; else if (calibration[i].center < 8192) calibration[i].center = 0; else if (calibration[i].center < 24576) calibration[i].center = 16383; else calibration[i].center = 32767; } } void JoyDevice::handle_event(SDL_Event *event) { if (event->type == SDL_JOYAXISMOTION) { int cal_min = calibration[event->jaxis.axis].min; int cal_max = calibration[event->jaxis.axis].max; int cal_cen = calibration[event->jaxis.axis].center; int t = gui_config->joystick_threshold; int ax_min = (cal_min - cal_cen) * t / 100 + cal_cen; int ax_max = (cal_max - cal_cen) * t / 100 + cal_cen; if (mode == JOY_MODE_INDIVIDUAL) { for (int i = 0; i < NUM_JOYPADS; i++) { Binding *pad = (Binding *)&(gui_config->pad[i]); for (int j = 0; j < NUM_JOYPAD_LINKS; j++) { if (pad[j].get_axis() == event->jaxis.axis && pad[j].get_device() == (unsigned int)(joynum + 1)) { t = pad[j].get_threshold(); if (pad[j].is_positive()) { ax_max = (cal_max - cal_cen) * t / 100 + cal_cen; } else if (pad[j].is_negative()) { ax_min = (cal_min - cal_cen) * t / 100 + cal_cen; } } } } for (int i = 0; i < NUM_EMU_LINKS; i++) { if (gui_config->shortcut[i].get_axis() == event->jaxis.axis && gui_config->shortcut[i].get_device() == (unsigned int)(joynum + 1)) { t = gui_config->shortcut[i].get_threshold(); if (gui_config->shortcut[i].is_positive()) { ax_max = (cal_max - cal_cen) * t / 100 + cal_cen; } else if (gui_config->shortcut[i].is_negative()) { ax_min = (cal_min - cal_cen) * t / 100 + cal_cen; } } } } else if (mode == JOY_MODE_CALIBRATE) { if (event->jaxis.value < calibration[event->jaxis.axis].min) calibration[event->jaxis.axis].min = event->jaxis.value; if (event->jaxis.value > calibration[event->jaxis.axis].max) calibration[event->jaxis.axis].min = event->jaxis.value; } /* Sanity Check */ if (ax_min >= cal_cen) ax_min = cal_cen - 1; if (ax_max <= cal_cen) ax_max = cal_cen + 1; if (event->jaxis.value <= ax_min && axis[event->jaxis.axis] > ax_min) { add_event(JOY_AXIS(event->jaxis.axis, AXIS_NEG), 1); } if (event->jaxis.value > ax_min && axis[event->jaxis.axis] <= ax_min) { add_event(JOY_AXIS(event->jaxis.axis, AXIS_NEG), 0); } if (event->jaxis.value >= ax_max && axis[event->jaxis.axis] < ax_max) { add_event(JOY_AXIS(event->jaxis.axis, AXIS_POS), 1); } if (event->jaxis.value < ax_max && axis[event->jaxis.axis] >= ax_max) { add_event(JOY_AXIS(event->jaxis.axis, AXIS_POS), 0); } axis[event->jaxis.axis] = event->jaxis.value; } else if (event->type == SDL_JOYBUTTONUP || event->type == SDL_JOYBUTTONDOWN) { add_event(event->jbutton.button, event->jbutton.state == SDL_PRESSED ? 1 : 0); } else if (event->type == SDL_JOYHATMOTION) { if ((event->jhat.value & SDL_HAT_UP) && !(hat[event->jhat.hat] & SDL_HAT_UP)) { add_event(JOY_AXIS(axis.size() + (event->jhat.hat * 2), AXIS_POS), 1); } if (!(event->jhat.value & SDL_HAT_UP) && (hat[event->jhat.hat] & SDL_HAT_UP)) { add_event(JOY_AXIS(axis.size() + (event->jhat.hat * 2), AXIS_POS), 0); } if ((event->jhat.value & SDL_HAT_DOWN) && !(hat[event->jhat.hat] & SDL_HAT_DOWN)) { add_event(JOY_AXIS(axis.size() + (event->jhat.hat * 2), AXIS_NEG), 1); } if (!(event->jhat.value & SDL_HAT_DOWN) && (hat[event->jhat.hat] & SDL_HAT_DOWN)) { add_event(JOY_AXIS(axis.size() + (event->jhat.hat * 2), AXIS_NEG), 0); } if ((event->jhat.value & SDL_HAT_LEFT) && !(hat[event->jhat.hat] & SDL_HAT_LEFT)) { add_event(JOY_AXIS(axis.size() + (event->jhat.hat * 2) + 1, AXIS_NEG), 1); } if (!(event->jhat.value & SDL_HAT_LEFT) && (hat[event->jhat.hat] & SDL_HAT_LEFT)) { add_event(JOY_AXIS(axis.size() + (event->jhat.hat * 2) + 1, AXIS_NEG), 0); } if ((event->jhat.value & SDL_HAT_RIGHT) && !(hat[event->jhat.hat] & SDL_HAT_RIGHT)) { add_event(JOY_AXIS(axis.size() + (event->jhat.hat * 2) + 1, AXIS_POS), 1); } if (!(event->jhat.value & SDL_HAT_RIGHT) && (hat[event->jhat.hat] & SDL_HAT_RIGHT)) { add_event(JOY_AXIS(axis.size() + (event->jhat.hat * 2) + 1, AXIS_POS), 0); } hat[event->jhat.hat] = event->jhat.value; } } int JoyDevice::get_event(JoyEvent *event) { if (queue.empty()) return 0; event->parameter = queue.front().parameter; event->state = queue.front().state; queue.pop(); return 1; } void JoyDevice::flush() { SDL_Event event; while (SDL_PollEvent(&event)) { } while (!queue.empty()) queue.pop(); } void JoyDevices::clear() { joysticks.clear(); } bool JoyDevices::add(int sdl_device_index) { std::array joynums; joynums.fill(false); for (auto &j : joysticks) { joynums[j.second->joynum] = true; } // New joystick always gets the lowest available joynum int joynum(0); for (; joynum < NUM_JOYPADS && joynums[joynum]; ++joynum); if (joynum == NUM_JOYPADS) { printf("Joystick slots are full, cannot add joystick (device index %d)\n", sdl_device_index); return false; } auto ujd = std::make_unique(); ujd->set_sdl_joystick(sdl_device_index, joynum); printf("Joystick %d, %s", ujd->joynum+1, ujd->description.c_str()); joysticks[ujd->instance_id] = std::move(ujd); return true; } bool JoyDevices::remove(SDL_JoystickID instance_id) { if (!joysticks.count(instance_id)) { printf("joystick_remove: invalid instance id %d", instance_id); return false; } printf("Removed joystick %d, %s", joysticks[instance_id]->joynum+1, joysticks[instance_id]->description.c_str()); joysticks.erase(instance_id); return true; } JoyDevice *JoyDevices::get_joystick(SDL_JoystickID instance_id) { if (joysticks.count(instance_id)){ return joysticks[instance_id].get(); } printf("BUG: Event for unknown joystick instance id: %d", instance_id); return NULL; } void JoyDevices::register_centers() { for (auto &j : joysticks) j.second->register_centers(); } void JoyDevices::flush_events() { for (auto &j : joysticks) j.second->flush(); } void JoyDevices::set_mode(int mode) { for (auto &j : joysticks) j.second->mode = mode; } void JoyDevices::poll_events() { SDL_Event event; JoyDevice *jd{}; while (SDL_PollEvent(&event)) { switch(event.type) { case SDL_JOYAXISMOTION: jd = get_joystick(event.jaxis.which); break; case SDL_JOYHATMOTION: jd = get_joystick(event.jhat.which); break; case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN: jd = get_joystick(event.jbutton.which); break; case SDL_JOYDEVICEADDED: add(event.jdevice.which); continue; case SDL_JOYDEVICEREMOVED: remove(event.jdevice.which); continue; } if (jd) { jd->handle_event(&event); } } }