2019-02-12 10:30:24 +00:00
|
|
|
/*
|
|
|
|
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 <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2020-03-28 16:58:01 +00:00
|
|
|
|
2019-02-12 10:30:24 +00:00
|
|
|
#include "gamepad_device.h"
|
2019-03-29 16:19:18 +00:00
|
|
|
#include "cfg/cfg.h"
|
2020-03-28 16:58:01 +00:00
|
|
|
#include "oslib/oslib.h"
|
|
|
|
#include "rend/gui.h"
|
2020-04-20 16:52:02 +00:00
|
|
|
#include "emulator.h"
|
2021-05-19 16:13:52 +00:00
|
|
|
#include "hw/maple/maple_devs.h"
|
2021-06-27 10:49:47 +00:00
|
|
|
#include "stdclass.h"
|
2020-03-28 16:58:01 +00:00
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <climits>
|
2021-06-27 10:49:47 +00:00
|
|
|
#include <fstream>
|
2019-03-29 16:19:18 +00:00
|
|
|
|
|
|
|
#define MAPLE_PORT_CFG_PREFIX "maple_"
|
2019-02-12 10:30:24 +00:00
|
|
|
|
2020-03-20 15:57:50 +00:00
|
|
|
// Gamepads
|
2020-12-02 13:40:50 +00:00
|
|
|
u32 kcode[4] = { ~0u, ~0u, ~0u, ~0u };
|
2020-03-20 15:57:50 +00:00
|
|
|
s8 joyx[4];
|
|
|
|
s8 joyy[4];
|
|
|
|
s8 joyrx[4];
|
|
|
|
s8 joyry[4];
|
|
|
|
u8 rt[4];
|
|
|
|
u8 lt[4];
|
2019-02-12 10:30:24 +00:00
|
|
|
|
2019-02-21 13:49:27 +00:00
|
|
|
std::vector<std::shared_ptr<GamepadDevice>> GamepadDevice::_gamepads;
|
2019-02-12 10:30:24 +00:00
|
|
|
std::mutex GamepadDevice::_gamepads_mutex;
|
|
|
|
|
2019-06-23 10:17:24 +00:00
|
|
|
#ifdef TEST_AUTOMATION
|
|
|
|
#include "hw/sh4/sh4_sched.h"
|
|
|
|
static FILE *record_input;
|
|
|
|
#endif
|
|
|
|
|
2019-02-13 19:29:49 +00:00
|
|
|
bool GamepadDevice::gamepad_btn_input(u32 code, bool pressed)
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
2020-04-17 15:55:43 +00:00
|
|
|
if (_input_detected != nullptr && _detecting_button
|
2019-03-28 17:28:29 +00:00
|
|
|
&& os_GetSeconds() >= _detection_start_time && pressed)
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
|
|
|
_input_detected(code);
|
2020-04-17 15:55:43 +00:00
|
|
|
_input_detected = nullptr;
|
2020-04-26 09:27:45 +00:00
|
|
|
return true;
|
2019-02-12 10:30:24 +00:00
|
|
|
}
|
2020-11-21 16:57:23 +00:00
|
|
|
if (!input_mapper || _maple_port < 0 || _maple_port > (int)ARRAY_SIZE(kcode))
|
2019-02-17 17:34:22 +00:00
|
|
|
return false;
|
2019-02-12 10:30:24 +00:00
|
|
|
|
2020-11-21 16:57:23 +00:00
|
|
|
auto handle_key = [&](u32 port, DreamcastKey key)
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
2020-11-21 16:57:23 +00:00
|
|
|
if (key == EMU_BTN_NONE)
|
|
|
|
return false;
|
|
|
|
|
2020-12-02 13:40:50 +00:00
|
|
|
if (key <= DC_BTN_RELOAD)
|
2020-11-21 16:57:23 +00:00
|
|
|
{
|
|
|
|
if (pressed)
|
2020-12-02 13:40:50 +00:00
|
|
|
kcode[port] &= ~key;
|
2020-11-21 16:57:23 +00:00
|
|
|
else
|
2020-12-02 13:40:50 +00:00
|
|
|
kcode[port] |= key;
|
2020-11-21 16:57:23 +00:00
|
|
|
#ifdef TEST_AUTOMATION
|
|
|
|
if (record_input != NULL)
|
|
|
|
fprintf(record_input, "%ld button %x %04x\n", sh4_sched_now64(), port, kcode[port]);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
2019-04-05 12:48:59 +00:00
|
|
|
{
|
|
|
|
switch (key)
|
|
|
|
{
|
2020-11-21 16:57:23 +00:00
|
|
|
case EMU_BTN_ESCAPE:
|
|
|
|
if (pressed)
|
|
|
|
dc_exit();
|
2019-04-05 12:48:59 +00:00
|
|
|
break;
|
2020-11-21 16:57:23 +00:00
|
|
|
case EMU_BTN_MENU:
|
|
|
|
if (pressed)
|
|
|
|
gui_open_settings();
|
2019-04-05 12:48:59 +00:00
|
|
|
break;
|
2020-11-21 16:57:23 +00:00
|
|
|
case EMU_BTN_FFORWARD:
|
2021-07-19 10:10:14 +00:00
|
|
|
if (pressed && !gui_is_open())
|
2021-03-19 18:31:01 +00:00
|
|
|
settings.input.fastForwardMode = !settings.input.fastForwardMode;
|
2019-04-05 12:48:59 +00:00
|
|
|
break;
|
2020-11-21 16:57:23 +00:00
|
|
|
case EMU_BTN_TRIGGER_LEFT:
|
|
|
|
lt[port] = pressed ? 255 : 0;
|
2019-04-05 12:48:59 +00:00
|
|
|
break;
|
2020-11-21 16:57:23 +00:00
|
|
|
case EMU_BTN_TRIGGER_RIGHT:
|
|
|
|
rt[port] = pressed ? 255 : 0;
|
2019-04-05 12:48:59 +00:00
|
|
|
break;
|
2020-11-21 16:57:23 +00:00
|
|
|
case EMU_BTN_ANA_UP:
|
|
|
|
case EMU_BTN_ANA_DOWN:
|
2021-09-07 08:54:00 +00:00
|
|
|
{
|
|
|
|
if (pressed)
|
|
|
|
digitalToAnalogState[port] |= key;
|
|
|
|
else
|
|
|
|
digitalToAnalogState[port] &= ~key;
|
|
|
|
const u32 upDown = digitalToAnalogState[port] & (EMU_BTN_ANA_UP | EMU_BTN_ANA_DOWN);
|
|
|
|
if (upDown == 0 || upDown == (EMU_BTN_ANA_UP | EMU_BTN_ANA_DOWN))
|
|
|
|
joyy[port] = 0;
|
|
|
|
else if (upDown == EMU_BTN_ANA_UP)
|
|
|
|
joyy[port] = -128;
|
|
|
|
else
|
|
|
|
joyy[port] = 127;
|
|
|
|
}
|
2019-04-05 12:48:59 +00:00
|
|
|
break;
|
2020-11-21 16:57:23 +00:00
|
|
|
case EMU_BTN_ANA_LEFT:
|
|
|
|
case EMU_BTN_ANA_RIGHT:
|
2021-09-07 08:54:00 +00:00
|
|
|
{
|
|
|
|
if (pressed)
|
|
|
|
digitalToAnalogState[port] |= key;
|
|
|
|
else
|
|
|
|
digitalToAnalogState[port] &= ~key;
|
|
|
|
const u32 leftRight = digitalToAnalogState[port] & (EMU_BTN_ANA_LEFT | EMU_BTN_ANA_RIGHT);
|
|
|
|
if (leftRight == 0 || leftRight == (EMU_BTN_ANA_LEFT | EMU_BTN_ANA_RIGHT))
|
|
|
|
joyx[port] = 0;
|
|
|
|
else if (leftRight == EMU_BTN_ANA_LEFT)
|
|
|
|
joyx[port] = -128;
|
|
|
|
else
|
|
|
|
joyx[port] = 127;
|
|
|
|
}
|
2019-04-05 12:48:59 +00:00
|
|
|
break;
|
2020-11-21 16:57:23 +00:00
|
|
|
default:
|
|
|
|
return false;
|
2019-04-05 12:48:59 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-21 16:57:23 +00:00
|
|
|
|
|
|
|
DEBUG_LOG(INPUT, "%d: BUTTON %s %x -> %d. kcode=%x", port, pressed ? "down" : "up", code, key, kcode[port]);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool rc = false;
|
|
|
|
if (_maple_port == 4)
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
2020-11-21 16:57:23 +00:00
|
|
|
for (int port = 0; port < 4; port++)
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
2020-11-21 16:57:23 +00:00
|
|
|
DreamcastKey key = input_mapper->get_button_id(port, code);
|
|
|
|
rc = handle_key(port, key) || rc;
|
2019-02-12 10:30:24 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-21 16:57:23 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
DreamcastKey key = input_mapper->get_button_id(0, code);
|
|
|
|
rc = handle_key(_maple_port, key);
|
|
|
|
}
|
2019-02-13 19:29:49 +00:00
|
|
|
|
2020-11-21 16:57:23 +00:00
|
|
|
return rc;
|
2019-02-12 10:30:24 +00:00
|
|
|
}
|
|
|
|
|
2019-02-13 19:29:49 +00:00
|
|
|
bool GamepadDevice::gamepad_axis_input(u32 code, int value)
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
2019-02-22 10:35:41 +00:00
|
|
|
s32 v;
|
2020-11-21 16:57:23 +00:00
|
|
|
if (input_mapper->get_axis_inverted(0, code))
|
2019-02-22 10:35:41 +00:00
|
|
|
v = (get_axis_min_value(code) + get_axis_range(code) - value) * 255 / get_axis_range(code) - 128;
|
|
|
|
else
|
2020-01-16 21:19:07 +00:00
|
|
|
v = (value - get_axis_min_value(code)) * 255 / get_axis_range(code) - 128; //-128 ... +127 range
|
2020-11-21 16:57:23 +00:00
|
|
|
if (_input_detected != NULL && !_detecting_button
|
2019-03-28 17:28:29 +00:00
|
|
|
&& os_GetSeconds() >= _detection_start_time && (v >= 64 || v <= -64))
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
|
|
|
_input_detected(code);
|
|
|
|
_input_detected = NULL;
|
2020-04-26 09:27:45 +00:00
|
|
|
return true;
|
2019-02-12 10:30:24 +00:00
|
|
|
}
|
2020-11-21 16:57:23 +00:00
|
|
|
if (!input_mapper || _maple_port < 0 || _maple_port > 4)
|
2019-02-13 19:29:49 +00:00
|
|
|
return false;
|
2019-02-12 10:30:24 +00:00
|
|
|
|
2020-11-21 16:57:23 +00:00
|
|
|
auto handle_axis = [&](u32 port, DreamcastKey key)
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
|
|
|
|
2020-11-21 16:57:23 +00:00
|
|
|
if ((int)key < 0x10000)
|
|
|
|
{
|
|
|
|
kcode[port] |= key | (key << 1);
|
|
|
|
if (v <= -64)
|
|
|
|
kcode[port] &= ~key;
|
|
|
|
else if (v >= 64)
|
|
|
|
kcode[port] &= ~(key << 1);
|
2019-02-12 10:30:24 +00:00
|
|
|
|
2020-11-21 16:57:23 +00:00
|
|
|
// printf("Mapped to %d %d %d\n",mo,kcode[port]&mo,kcode[port]&(mo*2));
|
|
|
|
}
|
|
|
|
else if (((int)key >> 16) == 1) // Triggers
|
2019-03-08 18:56:17 +00:00
|
|
|
{
|
2020-11-21 16:57:23 +00:00
|
|
|
//printf("T-AXIS %d Mapped to %d -> %d\n",key, value, v + 128);
|
|
|
|
|
|
|
|
if (key == DC_AXIS_LT)
|
|
|
|
lt[port] = (u8)(v + 128);
|
|
|
|
else if (key == DC_AXIS_RT)
|
|
|
|
rt[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;
|
|
|
|
switch (key)
|
|
|
|
{
|
|
|
|
case DC_AXIS_X:
|
|
|
|
this_axis = &joyx[port];
|
|
|
|
other_axis = &joyy[port];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DC_AXIS_Y:
|
|
|
|
this_axis = &joyy[port];
|
|
|
|
other_axis = &joyx[port];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DC_AXIS_X2:
|
|
|
|
this_axis = &joyrx[port];
|
|
|
|
other_axis = &joyry[port];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DC_AXIS_Y2:
|
|
|
|
this_axis = &joyry[port];
|
|
|
|
other_axis = &joyrx[port];
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Radial dead zone
|
|
|
|
// FIXME compute both axes at the same time
|
|
|
|
if ((float)(v * v + *other_axis * *other_axis) < input_mapper->dead_zone * input_mapper->dead_zone * 128.f * 128.f)
|
|
|
|
{
|
|
|
|
*this_axis = 0;
|
|
|
|
*other_axis = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
*this_axis = (s8)v;
|
2020-03-20 15:57:50 +00:00
|
|
|
}
|
2020-11-21 16:57:23 +00:00
|
|
|
else if (((int)key >> 16) == 4) // Map triggers to digital buttons
|
2019-03-08 18:56:17 +00:00
|
|
|
{
|
2020-11-21 16:57:23 +00:00
|
|
|
if (v <= -64)
|
|
|
|
kcode[port] |= (key & ~0x40000); // button released
|
|
|
|
else if (v >= 64)
|
|
|
|
kcode[port] &= ~(key & ~0x40000); // button pressed
|
2019-03-08 18:56:17 +00:00
|
|
|
}
|
|
|
|
else
|
2020-11-21 16:57:23 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool rc = false;
|
|
|
|
if (_maple_port == 4)
|
2020-04-07 00:33:05 +00:00
|
|
|
{
|
2020-11-21 16:57:23 +00:00
|
|
|
for (u32 port = 0; port < 4; port++)
|
|
|
|
{
|
|
|
|
DreamcastKey key = input_mapper->get_axis_id(port, code);
|
|
|
|
rc = handle_axis(port, key) || rc;
|
|
|
|
}
|
2020-04-07 00:33:05 +00:00
|
|
|
}
|
2019-02-13 19:29:49 +00:00
|
|
|
else
|
2020-11-21 16:57:23 +00:00
|
|
|
{
|
|
|
|
DreamcastKey key = input_mapper->get_axis_id(0, code);
|
|
|
|
rc = handle_axis(_maple_port, key);
|
|
|
|
}
|
2019-02-13 19:29:49 +00:00
|
|
|
|
2020-11-21 16:57:23 +00:00
|
|
|
return rc;
|
2019-02-12 10:30:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-04-17 15:55:43 +00:00
|
|
|
std::string GamepadDevice::make_mapping_filename(bool instance)
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
2019-02-12 14:56:44 +00:00
|
|
|
std::string mapping_file = api_name() + "_" + name();
|
2020-04-17 15:55:43 +00:00
|
|
|
if (instance)
|
|
|
|
mapping_file += "-" + _unique_id;
|
2019-02-12 10:30:24 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-06-27 10:49:47 +00:00
|
|
|
void GamepadDevice::verify_or_create_system_mappings()
|
|
|
|
{
|
|
|
|
std::string dc_name = make_mapping_filename(false, 0);
|
|
|
|
std::string arcade_name = make_mapping_filename(false, 2);
|
|
|
|
|
|
|
|
std::string dc_path = get_readonly_config_path(std::string("mappings/") + dc_name);
|
|
|
|
std::string arcade_path = get_readonly_config_path(std::string("mappings/") + arcade_name);
|
|
|
|
|
|
|
|
if (!file_exists(arcade_path))
|
|
|
|
{
|
|
|
|
save_mapping(2);
|
|
|
|
input_mapper->ClearMappings();
|
|
|
|
}
|
|
|
|
if (!file_exists(dc_path))
|
|
|
|
{
|
|
|
|
save_mapping(0);
|
|
|
|
input_mapper->ClearMappings();
|
|
|
|
}
|
|
|
|
|
|
|
|
find_mapping(DC_PLATFORM_DREAMCAST);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadDevice::load_system_mappings(int system)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < GetGamepadCount(); i++)
|
|
|
|
{
|
|
|
|
std::shared_ptr<GamepadDevice> gamepad = GetGamepad(i);
|
2021-07-05 07:59:46 +00:00
|
|
|
gamepad->find_mapping(system);
|
2021-06-27 10:49:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string GamepadDevice::make_mapping_filename(bool instance, int system)
|
|
|
|
{
|
|
|
|
std::string mapping_file = api_name() + "_" + name();
|
|
|
|
if (instance)
|
|
|
|
mapping_file += "-" + _unique_id;
|
|
|
|
if (system != 0)
|
|
|
|
mapping_file += "_arcade";
|
|
|
|
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(int system)
|
|
|
|
{
|
2021-09-07 08:54:00 +00:00
|
|
|
if (!_remappable)
|
|
|
|
return true;
|
2021-06-27 10:49:47 +00:00
|
|
|
std::string mapping_file;
|
|
|
|
mapping_file = make_mapping_filename(false, system);
|
|
|
|
|
|
|
|
// fall back on default flycast mapping filename if system profile not found
|
|
|
|
std::string system_mapping_path = get_readonly_config_path(std::string("mappings/") + mapping_file);
|
|
|
|
if (!file_exists(system_mapping_path))
|
|
|
|
mapping_file = make_mapping_filename(false);
|
|
|
|
|
2021-07-05 07:59:46 +00:00
|
|
|
std::shared_ptr<InputMapping> mapper = InputMapping::LoadMapping(mapping_file.c_str());
|
2021-06-27 10:49:47 +00:00
|
|
|
|
2021-07-05 07:59:46 +00:00
|
|
|
if (!mapper)
|
|
|
|
return false;
|
|
|
|
input_mapper = mapper;
|
|
|
|
return true;
|
2021-06-27 10:49:47 +00:00
|
|
|
}
|
|
|
|
|
2020-04-17 15:55:43 +00:00
|
|
|
bool GamepadDevice::find_mapping(const char *custom_mapping /* = nullptr */)
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
2021-09-07 08:54:00 +00:00
|
|
|
if (!_remappable)
|
|
|
|
return true;
|
2019-02-12 10:30:24 +00:00
|
|
|
std::string mapping_file;
|
2020-04-17 15:55:43 +00:00
|
|
|
if (custom_mapping != nullptr)
|
2019-02-12 10:30:24 +00:00
|
|
|
mapping_file = custom_mapping;
|
|
|
|
else
|
2020-04-17 15:55:43 +00:00
|
|
|
mapping_file = make_mapping_filename(true);
|
2019-02-12 10:30:24 +00:00
|
|
|
|
|
|
|
input_mapper = InputMapping::LoadMapping(mapping_file.c_str());
|
2020-04-17 15:55:43 +00:00
|
|
|
if (!input_mapper && custom_mapping == nullptr)
|
|
|
|
{
|
|
|
|
mapping_file = make_mapping_filename(false);
|
|
|
|
input_mapper = InputMapping::LoadMapping(mapping_file.c_str());
|
|
|
|
}
|
2020-03-12 15:09:05 +00:00
|
|
|
return !!input_mapper;
|
2019-02-12 10:30:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int GamepadDevice::GetGamepadCount()
|
|
|
|
{
|
|
|
|
_gamepads_mutex.lock();
|
|
|
|
int count = _gamepads.size();
|
|
|
|
_gamepads_mutex.unlock();
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2019-02-21 13:49:27 +00:00
|
|
|
std::shared_ptr<GamepadDevice> GamepadDevice::GetGamepad(int index)
|
2019-02-12 10:30:24 +00:00
|
|
|
{
|
|
|
|
_gamepads_mutex.lock();
|
2019-02-21 13:49:27 +00:00
|
|
|
std::shared_ptr<GamepadDevice> dev;
|
2020-03-20 15:57:50 +00:00
|
|
|
if (index >= 0 && index < (int)_gamepads.size())
|
2019-02-12 10:30:24 +00:00
|
|
|
dev = _gamepads[index];
|
|
|
|
else
|
|
|
|
dev = NULL;
|
|
|
|
_gamepads_mutex.unlock();
|
|
|
|
return dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadDevice::save_mapping()
|
|
|
|
{
|
2020-03-12 15:09:05 +00:00
|
|
|
if (!input_mapper)
|
2019-02-12 10:30:24 +00:00
|
|
|
return;
|
2020-03-12 15:09:05 +00:00
|
|
|
std::string filename = make_mapping_filename();
|
|
|
|
InputMapping::SaveMapping(filename.c_str(), input_mapper);
|
2019-02-12 10:30:24 +00:00
|
|
|
}
|
2019-02-22 18:23:03 +00:00
|
|
|
|
2021-06-27 10:49:47 +00:00
|
|
|
void GamepadDevice::save_mapping(int system)
|
|
|
|
{
|
|
|
|
if (!input_mapper)
|
|
|
|
return;
|
|
|
|
std::string filename = make_mapping_filename(false, system);
|
|
|
|
input_mapper->set_dirty();
|
|
|
|
InputMapping::SaveMapping(filename.c_str(), input_mapper);
|
|
|
|
}
|
|
|
|
|
2019-02-22 18:23:03 +00:00
|
|
|
void UpdateVibration(u32 port, float power, float inclination, u32 duration_ms)
|
|
|
|
{
|
|
|
|
int i = GamepadDevice::GetGamepadCount() - 1;
|
|
|
|
for ( ; i >= 0; i--)
|
|
|
|
{
|
|
|
|
std::shared_ptr<GamepadDevice> gamepad = GamepadDevice::GetGamepad(i);
|
2020-03-20 15:57:50 +00:00
|
|
|
if (gamepad != NULL && gamepad->maple_port() == (int)port && gamepad->is_rumble_enabled())
|
2019-02-22 18:23:03 +00:00
|
|
|
gamepad->rumble(power, inclination, duration_ms);
|
|
|
|
}
|
|
|
|
}
|
2019-03-28 17:28:29 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-06-23 10:17:24 +00:00
|
|
|
#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;
|
2020-04-03 14:32:53 +00:00
|
|
|
std::string game_dir = settings.imgread.ImagePath;
|
2019-06-23 10:17:24 +00:00
|
|
|
size_t slash = game_dir.find_last_of("/");
|
|
|
|
size_t dot = game_dir.find_last_of(".");
|
2020-04-03 14:32:53 +00:00
|
|
|
std::string input_file = "scripts/" + game_dir.substr(slash + 1, dot - slash) + "input";
|
2021-01-19 10:11:01 +00:00
|
|
|
return nowide::fopen(input_file.c_str(), write ? "w" : "r");
|
2019-06-23 10:17:24 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-03-13 13:13:39 +00:00
|
|
|
void GamepadDevice::Register(const std::shared_ptr<GamepadDevice>& gamepad)
|
2019-03-29 16:19:18 +00:00
|
|
|
{
|
|
|
|
int maple_port = cfgLoadInt("input",
|
2021-03-01 09:13:40 +00:00
|
|
|
MAPLE_PORT_CFG_PREFIX + gamepad->unique_id(), 12345);
|
2019-03-29 16:19:18 +00:00
|
|
|
if (maple_port != 12345)
|
|
|
|
gamepad->set_maple_port(maple_port);
|
2019-06-23 10:17:24 +00:00
|
|
|
#ifdef TEST_AUTOMATION
|
|
|
|
if (record_input == NULL)
|
|
|
|
{
|
|
|
|
record_input = get_record_input(true);
|
|
|
|
if (record_input != NULL)
|
|
|
|
setbuf(record_input, NULL);
|
|
|
|
}
|
|
|
|
#endif
|
2019-03-29 16:19:18 +00:00
|
|
|
_gamepads_mutex.lock();
|
|
|
|
_gamepads.push_back(gamepad);
|
|
|
|
_gamepads_mutex.unlock();
|
|
|
|
}
|
|
|
|
|
2021-03-13 13:13:39 +00:00
|
|
|
void GamepadDevice::Unregister(const std::shared_ptr<GamepadDevice>& gamepad)
|
2019-03-29 16:19:18 +00:00
|
|
|
{
|
|
|
|
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<GamepadDevice> gamepad = GamepadDevice::GetGamepad(i);
|
|
|
|
if (gamepad != NULL && !gamepad->unique_id().empty())
|
2021-03-01 09:13:40 +00:00
|
|
|
cfgSaveInt("input", MAPLE_PORT_CFG_PREFIX + gamepad->unique_id(), gamepad->maple_port());
|
2019-03-29 16:19:18 +00:00
|
|
|
}
|
|
|
|
}
|
2019-06-23 10:17:24 +00:00
|
|
|
|
2021-05-19 16:13:52 +00:00
|
|
|
void Mouse::setAbsPos(int x, int y, int width, int height) {
|
|
|
|
SetMousePosition(x, y, width, height, maple_port());
|
|
|
|
}
|
|
|
|
|
2021-09-14 09:45:27 +00:00
|
|
|
void Mouse::setRelPos(float deltax, float deltay) {
|
2021-05-19 16:13:52 +00:00
|
|
|
SetRelativeMousePosition(deltax, deltay, maple_port());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mouse::setWheel(int delta) {
|
2021-07-20 17:21:11 +00:00
|
|
|
if (maple_port() >= 0 && maple_port() < (int)ARRAY_SIZE(mo_wheel_delta))
|
2021-05-19 16:13:52 +00:00
|
|
|
mo_wheel_delta[maple_port()] += delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Mouse::setButton(Button button, bool pressed)
|
|
|
|
{
|
2021-07-20 17:21:11 +00:00
|
|
|
if (maple_port() >= 0 && maple_port() < (int)ARRAY_SIZE(mo_buttons))
|
2021-05-19 16:13:52 +00:00
|
|
|
{
|
|
|
|
if (pressed)
|
|
|
|
mo_buttons[maple_port()] &= ~(1 << (int)button);
|
|
|
|
else
|
|
|
|
mo_buttons[maple_port()] |= 1 << (int)button;
|
|
|
|
}
|
|
|
|
if (gui_is_open() && !is_detecting_input())
|
|
|
|
// Don't register mouse clicks as gamepad presses when gui is open
|
|
|
|
// This makes the gamepad presses to be handled first and the mouse position to be ignored
|
|
|
|
return;
|
|
|
|
gamepad_btn_input(button, pressed);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SystemMouse::setAbsPos(int x, int y, int width, int height) {
|
|
|
|
gui_set_mouse_position(x, y);
|
|
|
|
Mouse::setAbsPos(x, y, width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SystemMouse::setButton(Button button, bool pressed) {
|
|
|
|
int uiBtn = (int)button - 1;
|
|
|
|
if (uiBtn < 2)
|
|
|
|
uiBtn ^= 1;
|
|
|
|
gui_set_mouse_button(uiBtn, pressed);
|
|
|
|
Mouse::setButton(button, pressed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SystemMouse::setWheel(int delta) {
|
|
|
|
gui_set_mouse_wheel(delta * 35);
|
|
|
|
Mouse::setWheel(delta);
|
|
|
|
}
|
|
|
|
|
2019-06-23 10:17:24 +00:00
|
|
|
#ifdef TEST_AUTOMATION
|
2021-03-01 09:13:40 +00:00
|
|
|
#include "cfg/option.h"
|
2019-06-23 10:17:24 +00:00
|
|
|
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;
|
|
|
|
}
|
2019-08-14 20:35:07 +00:00
|
|
|
u64 now = sh4_sched_now64();
|
2021-03-01 09:13:40 +00:00
|
|
|
if (config::UseReios)
|
2019-08-14 20:35:07 +00:00
|
|
|
{
|
|
|
|
// Account for the swirl time
|
2021-03-01 09:13:40 +00:00
|
|
|
if (config::Broadcast == 0)
|
2019-08-14 20:35:07 +00:00
|
|
|
now = std::max((int64_t)now - 2152626532L, 0L);
|
|
|
|
else
|
|
|
|
now = std::max((int64_t)now - 2191059108L, 0L);
|
|
|
|
}
|
2019-06-23 10:17:24 +00:00
|
|
|
if (replay_file == NULL)
|
|
|
|
{
|
2019-08-14 20:35:07 +00:00
|
|
|
if (next_event > 0 && now - next_event > SH4_MAIN_CLOCK * 5)
|
2019-06-23 10:17:24 +00:00
|
|
|
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;
|
2019-07-01 15:17:08 +00:00
|
|
|
NOTICE_LOG(INPUT, "Input replay terminated");
|
2019-06-23 10:17:24 +00:00
|
|
|
do_screenshot = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|