/* Copyright 2019 flyinghead This file is part of reicast. reicast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. reicast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with reicast. If not, see . */ #include #include #include "gamepad_device.h" #include "rend/gui.h" #include "oslib/oslib.h" #include "cfg/cfg.h" #define MAPLE_PORT_CFG_PREFIX "maple_" extern void dc_exit(); extern u16 kcode[4]; extern u8 rt[4], lt[4]; extern s8 joyx[4], joyy[4]; std::vector> GamepadDevice::_gamepads; std::mutex GamepadDevice::_gamepads_mutex; #ifdef TEST_AUTOMATION #include "hw/sh4/sh4_sched.h" static FILE *record_input; #endif bool GamepadDevice::gamepad_btn_input(u32 code, bool pressed) { if (_input_detected != NULL && _detecting_button && os_GetSeconds() >= _detection_start_time && pressed) { _input_detected(code); _input_detected = NULL; } if (input_mapper == NULL || _maple_port < 0 || _maple_port >= ARRAY_SIZE(kcode)) return false; DreamcastKey key = input_mapper->get_button_id(code); if (key == EMU_BTN_NONE) return false; if (key < 0x10000) { if (pressed) { kcode[_maple_port] &= ~(u16)key; // Avoid two opposite dpad keys being pressed simultaneously switch (key) { case DC_DPAD_UP: kcode[_maple_port] |= (u16)DC_DPAD_DOWN; break; case DC_DPAD_DOWN: kcode[_maple_port] |= (u16)DC_DPAD_UP; break; case DC_DPAD_LEFT: kcode[_maple_port] |= (u16)DC_DPAD_RIGHT; break; case DC_DPAD_RIGHT: kcode[_maple_port] |= (u16)DC_DPAD_LEFT; break; case DC_DPAD2_UP: kcode[_maple_port] |= (u16)DC_DPAD2_DOWN; break; case DC_DPAD2_DOWN: kcode[_maple_port] |= (u16)DC_DPAD2_UP; break; case DC_DPAD2_LEFT: kcode[_maple_port] |= (u16)DC_DPAD2_RIGHT; break; case DC_DPAD2_RIGHT: kcode[_maple_port] |= (u16)DC_DPAD2_LEFT; break; default: break; } } else kcode[_maple_port] |= (u16)key; #ifdef TEST_AUTOMATION if (record_input != NULL) fprintf(record_input, "%ld button %x %04x\n", sh4_sched_now64(), _maple_port, kcode[_maple_port]); #endif } else { switch (key) { case EMU_BTN_ESCAPE: if (pressed) dc_exit(); break; case EMU_BTN_MENU: if (pressed) gui_open_settings(); break; case EMU_BTN_TRIGGER_LEFT: lt[_maple_port] = pressed ? 255 : 0; break; case EMU_BTN_TRIGGER_RIGHT: rt[_maple_port] = pressed ? 255 : 0; break; default: return false; } } //printf("%d: BUTTON %s %x -> %d. kcode=%x\n", _maple_port, pressed ? "down" : "up", code, key, kcode[_maple_port]); return true; } bool GamepadDevice::gamepad_axis_input(u32 code, int value) { s32 v; if (input_mapper->get_axis_inverted(code)) v = (get_axis_min_value(code) + get_axis_range(code) - value) * 255 / get_axis_range(code) - 128; else v = (value - get_axis_min_value(code)) * 255 / get_axis_range(code) - 128; //-128 ... + 127 range if (_input_detected != NULL && !_detecting_button && os_GetSeconds() >= _detection_start_time && (v >= 64 || v <= -64)) { _input_detected(code); _input_detected = NULL; } if (input_mapper == NULL || _maple_port < 0 || _maple_port >= ARRAY_SIZE(kcode)) return false; DreamcastKey key = input_mapper->get_axis_id(code); if ((int)key < 0x10000) { kcode[_maple_port] |= key | (key << 1); if (v <= -64) kcode[_maple_port] &= ~key; else if (v >= 64) kcode[_maple_port] &= ~(key << 1); // printf("Mapped to %d %d %d\n",mo,kcode[port]&mo,kcode[port]&(mo*2)); } else if (((int)key >> 16) == 1) // Triggers { //printf("T-AXIS %d Mapped to %d -> %d\n",key, value, v + 128); if (key == DC_AXIS_LT) lt[_maple_port] = (u8)(v + 128); else if (key == DC_AXIS_RT) rt[_maple_port] = (u8)(v + 128); else return false; } else if (((int)key >> 16) == 2) // Analog axes { //printf("AXIS %d Mapped to %d -> %d\n", key, value, v); s8 *this_axis; s8 *other_axis; if (key == DC_AXIS_X) { this_axis = &joyx[_maple_port]; other_axis = &joyy[_maple_port]; } else if (key == DC_AXIS_Y) { this_axis = &joyy[_maple_port]; other_axis = &joyx[_maple_port]; } else return false; // Radial dead zone // FIXME compute both axes at the same time if ((float)(v * v + *other_axis * *other_axis) < _dead_zone * _dead_zone * 128.f * 128.f * 2.f) { *this_axis = 0; *other_axis = 0; } else *this_axis = (s8)v; } else return false; return true; } int GamepadDevice::get_axis_min_value(u32 axis) { auto it = axis_min_values.find(axis); if (it == axis_min_values.end()) { load_axis_min_max(axis); it = axis_min_values.find(axis); if (it == axis_min_values.end()) return INT_MIN; } return it->second; } unsigned int GamepadDevice::get_axis_range(u32 axis) { auto it = axis_ranges.find(axis); if (it == axis_ranges.end()) { load_axis_min_max(axis); it = axis_ranges.find(axis); if (it == axis_ranges.end()) return UINT_MAX; } return it->second; } std::string GamepadDevice::make_mapping_filename() { std::string mapping_file = api_name() + "_" + name(); std::replace(mapping_file.begin(), mapping_file.end(), '/', '-'); std::replace(mapping_file.begin(), mapping_file.end(), '\\', '-'); std::replace(mapping_file.begin(), mapping_file.end(), ':', '-'); std::replace(mapping_file.begin(), mapping_file.end(), '?', '-'); std::replace(mapping_file.begin(), mapping_file.end(), '*', '-'); std::replace(mapping_file.begin(), mapping_file.end(), '|', '-'); std::replace(mapping_file.begin(), mapping_file.end(), '"', '-'); std::replace(mapping_file.begin(), mapping_file.end(), '<', '-'); std::replace(mapping_file.begin(), mapping_file.end(), '>', '-'); mapping_file += ".cfg"; return mapping_file; } bool GamepadDevice::find_mapping(const char *custom_mapping /* = NULL */) { std::string mapping_file; if (custom_mapping != NULL) mapping_file = custom_mapping; else mapping_file = make_mapping_filename(); input_mapper = InputMapping::LoadMapping(mapping_file.c_str()); return input_mapper != NULL; } int GamepadDevice::GetGamepadCount() { _gamepads_mutex.lock(); int count = _gamepads.size(); _gamepads_mutex.unlock(); return count; } std::shared_ptr GamepadDevice::GetGamepad(int index) { _gamepads_mutex.lock(); std::shared_ptr dev; if (index >= 0 && index < _gamepads.size()) dev = _gamepads[index]; else dev = NULL; _gamepads_mutex.unlock(); return dev; } void GamepadDevice::save_mapping() { if (input_mapper == NULL) return; input_mapper->save(make_mapping_filename().c_str()); } void UpdateVibration(u32 port, float power, float inclination, u32 duration_ms) { int i = GamepadDevice::GetGamepadCount() - 1; for ( ; i >= 0; i--) { std::shared_ptr gamepad = GamepadDevice::GetGamepad(i); if (gamepad != NULL && gamepad->maple_port() == port && gamepad->is_rumble_enabled()) gamepad->rumble(power, inclination, duration_ms); } } void GamepadDevice::detect_btn_input(input_detected_cb button_pressed) { _input_detected = button_pressed; _detecting_button = true; _detection_start_time = os_GetSeconds() + 0.2; } void GamepadDevice::detect_axis_input(input_detected_cb axis_moved) { _input_detected = axis_moved; _detecting_button = false; _detection_start_time = os_GetSeconds() + 0.2; } #ifdef TEST_AUTOMATION static FILE *get_record_input(bool write) { if (write && !cfgLoadBool("record", "record_input", false)) return NULL; if (!write && !cfgLoadBool("record", "replay_input", false)) return NULL; string game_dir = cfgLoadStr("config", "image", ""); size_t slash = game_dir.find_last_of("/"); size_t dot = game_dir.find_last_of("."); string input_file = "scripts/" + game_dir.substr(slash + 1, dot - slash) + "input"; return fopen(input_file.c_str(), write ? "w" : "r"); } #endif void GamepadDevice::Register(std::shared_ptr gamepad) { int maple_port = cfgLoadInt("input", (MAPLE_PORT_CFG_PREFIX + gamepad->unique_id()).c_str(), 12345); if (maple_port != 12345) gamepad->set_maple_port(maple_port); #ifdef TEST_AUTOMATION if (record_input == NULL) { record_input = get_record_input(true); if (record_input != NULL) setbuf(record_input, NULL); } #endif _gamepads_mutex.lock(); _gamepads.push_back(gamepad); _gamepads_mutex.unlock(); } void GamepadDevice::Unregister(std::shared_ptr gamepad) { gamepad->save_mapping(); _gamepads_mutex.lock(); for (auto it = _gamepads.begin(); it != _gamepads.end(); it++) if (*it == gamepad) { _gamepads.erase(it); break; } _gamepads_mutex.unlock(); } void GamepadDevice::SaveMaplePorts() { for (int i = 0; i < GamepadDevice::GetGamepadCount(); i++) { std::shared_ptr gamepad = GamepadDevice::GetGamepad(i); if (gamepad != NULL && !gamepad->unique_id().empty()) cfgSaveInt("input", (MAPLE_PORT_CFG_PREFIX + gamepad->unique_id()).c_str(), gamepad->maple_port()); } } #ifdef TEST_AUTOMATION static bool replay_inited; FILE *replay_file; u64 next_event; u32 next_port; u32 next_kcode; bool do_screenshot; void replay_input() { if (!replay_inited) { replay_file = get_record_input(false); replay_inited = true; } u64 now = sh4_sched_now64(); if (settings.bios.UseReios) { // Account for the swirl time if (settings.dreamcast.broadcast == 0) now = std::max((int64_t)now - 2152626532L, 0L); else now = std::max((int64_t)now - 2191059108L, 0L); } if (replay_file == NULL) { if (next_event > 0 && now - next_event > SH4_MAIN_CLOCK * 5) die("Automation time-out after 5 s\n"); return; } while (next_event <= now) { if (next_event > 0) kcode[next_port] = next_kcode; char action[32]; if (fscanf(replay_file, "%ld %s %x %x\n", &next_event, action, &next_port, &next_kcode) != 4) { fclose(replay_file); replay_file = NULL; NOTICE_LOG(INPUT, "Input replay terminated"); do_screenshot = true; break; } } } #endif