mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
ui: Add controller/keyboard input mapping configuration
This commit is contained in:
@@ -55,6 +55,7 @@ input:
|
||||
auto_bind:
|
||||
type: bool
|
||||
default: true
|
||||
# Deprecated: use 'enable_rumble' on the controller mapping settings instead
|
||||
allow_vibration:
|
||||
type: bool
|
||||
default: true
|
||||
@@ -136,6 +137,91 @@ input:
|
||||
rtrigger:
|
||||
type: integer
|
||||
default: 18 # w
|
||||
gamepad_mappings:
|
||||
type: array
|
||||
items:
|
||||
gamepad_id: string
|
||||
enable_rumble:
|
||||
type: bool
|
||||
default: true
|
||||
# steel_battalion_mapping:
|
||||
# ...
|
||||
controller_mapping:
|
||||
a:
|
||||
type: integer
|
||||
default: 0
|
||||
b:
|
||||
type: integer
|
||||
default: 1
|
||||
x:
|
||||
type: integer
|
||||
default: 2
|
||||
y:
|
||||
type: integer
|
||||
default: 3
|
||||
back:
|
||||
type: integer
|
||||
default: 4
|
||||
guide:
|
||||
type: integer
|
||||
default: 5
|
||||
start:
|
||||
type: integer
|
||||
default: 6
|
||||
lstick_btn:
|
||||
type: integer
|
||||
default: 7
|
||||
rstick_btn:
|
||||
type: integer
|
||||
default: 8
|
||||
lshoulder:
|
||||
type: integer
|
||||
default: 9
|
||||
rshoulder:
|
||||
type: integer
|
||||
default: 10
|
||||
dpad_up:
|
||||
type: integer
|
||||
default: 11
|
||||
dpad_down:
|
||||
type: integer
|
||||
default: 12
|
||||
dpad_left:
|
||||
type: integer
|
||||
default: 13
|
||||
dpad_right:
|
||||
type: integer
|
||||
default: 14
|
||||
axis_left_x:
|
||||
type: integer
|
||||
default: 0
|
||||
axis_left_y:
|
||||
type: integer
|
||||
default: 1
|
||||
axis_right_x:
|
||||
type: integer
|
||||
default: 2
|
||||
axis_right_y:
|
||||
type: integer
|
||||
default: 3
|
||||
axis_trigger_left:
|
||||
type: integer
|
||||
default: 4
|
||||
axis_trigger_right:
|
||||
type: integer
|
||||
default: 5
|
||||
invert_axis_left_x:
|
||||
type: bool
|
||||
default: false
|
||||
invert_axis_left_y:
|
||||
type: bool
|
||||
default: false
|
||||
invert_axis_right_x:
|
||||
type: bool
|
||||
default: false
|
||||
invert_axis_right_y:
|
||||
type: bool
|
||||
default: false
|
||||
|
||||
display:
|
||||
renderer:
|
||||
|
||||
@@ -28,6 +28,7 @@ xemu_ss.add(files(
|
||||
'xemu-monitor.c',
|
||||
'xemu-net.c',
|
||||
'xemu-settings.cc',
|
||||
'xemu-controllers.cc',
|
||||
|
||||
'xemu.c',
|
||||
'xemu-data.c',
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* xemu Controller Binding Management
|
||||
*
|
||||
* Copyright (C) 2025 Matt Borgerson
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "xemu-controllers.h"
|
||||
#include "xemu-settings.h"
|
||||
#include <assert.h>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
constexpr int controller_button_count = 15;
|
||||
constexpr int controller_axes_count = 6;
|
||||
|
||||
RebindEventResult
|
||||
ControllerKeyboardRebindingMap::ConsumeRebindEvent(SDL_Event *event)
|
||||
{
|
||||
// Bind on key up
|
||||
// This ensures the UI does not immediately respond once the new binding is
|
||||
// applied
|
||||
if (event->type == SDL_KEYUP) {
|
||||
*(g_keyboard_scancode_map[m_table_row]) = event->key.keysym.scancode;
|
||||
return RebindEventResult::Complete;
|
||||
}
|
||||
|
||||
return RebindEventResult::Ignore;
|
||||
}
|
||||
|
||||
RebindEventResult ControllerGamepadRebindingMap::HandleButtonEvent(
|
||||
SDL_ControllerButtonEvent *event)
|
||||
{
|
||||
if (m_state->sdl_joystick_id != event->which) {
|
||||
return RebindEventResult::Ignore;
|
||||
}
|
||||
|
||||
int *button_map[controller_button_count] = {
|
||||
&m_state->controller_map->controller_mapping.a,
|
||||
&m_state->controller_map->controller_mapping.b,
|
||||
&m_state->controller_map->controller_mapping.x,
|
||||
&m_state->controller_map->controller_mapping.y,
|
||||
&m_state->controller_map->controller_mapping.back,
|
||||
&m_state->controller_map->controller_mapping.guide,
|
||||
&m_state->controller_map->controller_mapping.start,
|
||||
&m_state->controller_map->controller_mapping.lstick_btn,
|
||||
&m_state->controller_map->controller_mapping.rstick_btn,
|
||||
&m_state->controller_map->controller_mapping.lshoulder,
|
||||
&m_state->controller_map->controller_mapping.rshoulder,
|
||||
&m_state->controller_map->controller_mapping.dpad_up,
|
||||
&m_state->controller_map->controller_mapping.dpad_down,
|
||||
&m_state->controller_map->controller_mapping.dpad_left,
|
||||
&m_state->controller_map->controller_mapping.dpad_right,
|
||||
};
|
||||
|
||||
// FIXME: Allow face buttons to map to axes
|
||||
if (m_table_row >= controller_button_count) {
|
||||
return RebindEventResult::Ignore;
|
||||
}
|
||||
|
||||
// If we only track up events, then we might rebind to a button
|
||||
// that was already held down when the rebinding event began
|
||||
if (event->type == SDL_CONTROLLERBUTTONDOWN) {
|
||||
m_seen_key_down = true;
|
||||
return RebindEventResult::Ignore;
|
||||
}
|
||||
|
||||
// Bind on controller button up
|
||||
// This ensures the UI does not immediately respond once the new binding is
|
||||
// applied
|
||||
if (event->type != SDL_CONTROLLERBUTTONUP || !m_seen_key_down) {
|
||||
return RebindEventResult::Ignore;
|
||||
}
|
||||
|
||||
*(button_map[m_table_row]) = event->button;
|
||||
|
||||
return RebindEventResult::Complete;
|
||||
}
|
||||
|
||||
RebindEventResult
|
||||
ControllerGamepadRebindingMap::HandleAxisEvent(SDL_ControllerAxisEvent *event)
|
||||
{
|
||||
if (m_state->sdl_joystick_id != event->which) {
|
||||
return RebindEventResult::Ignore;
|
||||
}
|
||||
|
||||
// Axis inputs cannot be bound to controller buttons
|
||||
if (m_table_row < controller_button_count) {
|
||||
return RebindEventResult::Ignore;
|
||||
}
|
||||
|
||||
// Requre that the input be sufficiently outside of any deadzone range
|
||||
// before using it for rebinding
|
||||
if (std::abs(event->value >> 1) <=
|
||||
(std::numeric_limits<Sint16>::max() >> 2)) {
|
||||
return RebindEventResult::Ignore;
|
||||
}
|
||||
|
||||
int *axis_map[controller_axes_count] = {
|
||||
&m_state->controller_map->controller_mapping.axis_left_x,
|
||||
&m_state->controller_map->controller_mapping.axis_left_y,
|
||||
&m_state->controller_map->controller_mapping.axis_right_x,
|
||||
&m_state->controller_map->controller_mapping.axis_right_y,
|
||||
&m_state->controller_map->controller_mapping.axis_trigger_left,
|
||||
&m_state->controller_map->controller_mapping.axis_trigger_right,
|
||||
};
|
||||
|
||||
*(axis_map[m_table_row - controller_button_count]) = event->axis;
|
||||
|
||||
return RebindEventResult::Complete;
|
||||
}
|
||||
|
||||
RebindEventResult
|
||||
ControllerGamepadRebindingMap::ConsumeRebindEvent(SDL_Event *event)
|
||||
{
|
||||
switch (event->type) {
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
return (m_state->sdl_joystick_id == event->cdevice.which) ?
|
||||
RebindEventResult::Complete :
|
||||
RebindEventResult::Ignore;
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
return HandleButtonEvent(&event->cbutton);
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
return HandleAxisEvent(&event->caxis);
|
||||
default:
|
||||
return RebindEventResult::Ignore;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* xemu Settings Management
|
||||
*
|
||||
* Copyright (C) 2025 Matt Borgerson
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef XEMU_CONTROLLERS_H
|
||||
#define XEMU_CONTROLLERS_H
|
||||
|
||||
#include "xemu-input.h"
|
||||
#include <SDL.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
enum class RebindEventResult {
|
||||
Ignore,
|
||||
Complete,
|
||||
};
|
||||
|
||||
struct RebindingMap {
|
||||
protected:
|
||||
int m_table_row;
|
||||
RebindingMap(int table_row) : m_table_row{ table_row }
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
virtual RebindEventResult ConsumeRebindEvent(SDL_Event *event) = 0;
|
||||
|
||||
int GetTableRow() const
|
||||
{
|
||||
return m_table_row;
|
||||
}
|
||||
|
||||
virtual ~RebindingMap() = default;
|
||||
};
|
||||
|
||||
struct ControllerKeyboardRebindingMap : public virtual RebindingMap {
|
||||
RebindEventResult ConsumeRebindEvent(SDL_Event *event) override;
|
||||
|
||||
ControllerKeyboardRebindingMap(int table_row) : RebindingMap(table_row)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class ControllerGamepadRebindingMap : public virtual RebindingMap {
|
||||
ControllerState *m_state;
|
||||
bool m_seen_key_down;
|
||||
|
||||
RebindEventResult HandleButtonEvent(SDL_ControllerButtonEvent *event);
|
||||
RebindEventResult HandleAxisEvent(SDL_ControllerAxisEvent *event);
|
||||
|
||||
public:
|
||||
RebindEventResult ConsumeRebindEvent(SDL_Event *event) override;
|
||||
ControllerGamepadRebindingMap(int table_row, ControllerState *state)
|
||||
: RebindingMap(table_row), m_state{ state }, m_seen_key_down{ false }
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
extern int *g_keyboard_scancode_map[25];
|
||||
|
||||
GamepadMappings *xemu_settings_load_gamepad_mapping(const char *guid, bool reset_to_default);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // XEMU_CONTROLLERS_H
|
||||
+204
-77
@@ -28,9 +28,12 @@
|
||||
#include "qemu/timer.h"
|
||||
#include "qemu/config-file.h"
|
||||
|
||||
#include "xemu-controllers.h"
|
||||
#include "xemu-input.h"
|
||||
#include "xemu-notifications.h"
|
||||
#include "xemu-settings.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "sysemu/blockdev.h"
|
||||
|
||||
@@ -126,7 +129,90 @@ static const char **peripheral_params_settings_map[4][2] = {
|
||||
&g_config.input.peripherals.port4.peripheral_param_1 }
|
||||
};
|
||||
|
||||
static int sdl_kbd_scancode_map[25];
|
||||
int *g_keyboard_scancode_map[25] = {
|
||||
&g_config.input.keyboard_controller_scancode_map.a,
|
||||
&g_config.input.keyboard_controller_scancode_map.b,
|
||||
&g_config.input.keyboard_controller_scancode_map.x,
|
||||
&g_config.input.keyboard_controller_scancode_map.y,
|
||||
&g_config.input.keyboard_controller_scancode_map.back,
|
||||
&g_config.input.keyboard_controller_scancode_map.guide,
|
||||
&g_config.input.keyboard_controller_scancode_map.start,
|
||||
&g_config.input.keyboard_controller_scancode_map.lstick_btn,
|
||||
&g_config.input.keyboard_controller_scancode_map.rstick_btn,
|
||||
&g_config.input.keyboard_controller_scancode_map.white,
|
||||
&g_config.input.keyboard_controller_scancode_map.black,
|
||||
&g_config.input.keyboard_controller_scancode_map.dpad_up,
|
||||
&g_config.input.keyboard_controller_scancode_map.dpad_down,
|
||||
&g_config.input.keyboard_controller_scancode_map.dpad_left,
|
||||
&g_config.input.keyboard_controller_scancode_map.dpad_right,
|
||||
&g_config.input.keyboard_controller_scancode_map.lstick_up,
|
||||
&g_config.input.keyboard_controller_scancode_map.lstick_left,
|
||||
&g_config.input.keyboard_controller_scancode_map.lstick_right,
|
||||
&g_config.input.keyboard_controller_scancode_map.lstick_down,
|
||||
&g_config.input.keyboard_controller_scancode_map.ltrigger,
|
||||
&g_config.input.keyboard_controller_scancode_map.rstick_up,
|
||||
&g_config.input.keyboard_controller_scancode_map.rstick_left,
|
||||
&g_config.input.keyboard_controller_scancode_map.rstick_right,
|
||||
&g_config.input.keyboard_controller_scancode_map.rstick_down,
|
||||
&g_config.input.keyboard_controller_scancode_map.rtrigger,
|
||||
};
|
||||
|
||||
static void check_and_reset_in_range(int *btn, int min, int max,
|
||||
const char *message)
|
||||
{
|
||||
if (*btn < min || *btn >= max) {
|
||||
fprintf(stderr, "%s\n", message);
|
||||
*btn = min;
|
||||
}
|
||||
}
|
||||
|
||||
static void xemu_input_bindings_reload_controller_map(ControllerState *con, bool reset_to_default)
|
||||
{
|
||||
assert(con->type == INPUT_DEVICE_SDL_GAMECONTROLLER);
|
||||
|
||||
char guid[35] = { 0 };
|
||||
SDL_JoystickGetGUIDString(con->sdl_joystick_guid, guid, sizeof(guid));
|
||||
con->controller_map = xemu_settings_load_gamepad_mapping(guid, reset_to_default);
|
||||
|
||||
#define CHECK_RESET_BUTTON(btn) \
|
||||
check_and_reset_in_range(&con->controller_map->controller_mapping.btn, \
|
||||
SDL_CONTROLLER_BUTTON_INVALID, \
|
||||
SDL_CONTROLLER_BUTTON_MAX, \
|
||||
"Invalid entry for button " #btn ", resetting")
|
||||
|
||||
CHECK_RESET_BUTTON(a);
|
||||
CHECK_RESET_BUTTON(b);
|
||||
CHECK_RESET_BUTTON(x);
|
||||
CHECK_RESET_BUTTON(y);
|
||||
CHECK_RESET_BUTTON(dpad_left);
|
||||
CHECK_RESET_BUTTON(dpad_up);
|
||||
CHECK_RESET_BUTTON(dpad_right);
|
||||
CHECK_RESET_BUTTON(dpad_down);
|
||||
CHECK_RESET_BUTTON(back);
|
||||
CHECK_RESET_BUTTON(start);
|
||||
CHECK_RESET_BUTTON(lshoulder);
|
||||
CHECK_RESET_BUTTON(rshoulder);
|
||||
CHECK_RESET_BUTTON(lstick_btn);
|
||||
CHECK_RESET_BUTTON(rstick_btn);
|
||||
CHECK_RESET_BUTTON(guide);
|
||||
|
||||
#undef CHECK_RESET_BUTTON
|
||||
|
||||
#define CHECK_RESET_AXIS(axis) \
|
||||
check_and_reset_in_range(&con->controller_map->controller_mapping.axis, \
|
||||
SDL_CONTROLLER_AXIS_INVALID, \
|
||||
SDL_CONTROLLER_AXIS_MAX, \
|
||||
"Invalid entry for button " #axis ", resetting")
|
||||
|
||||
CHECK_RESET_AXIS(axis_trigger_left);
|
||||
CHECK_RESET_AXIS(axis_trigger_right);
|
||||
CHECK_RESET_AXIS(axis_left_x);
|
||||
CHECK_RESET_AXIS(axis_left_y);
|
||||
CHECK_RESET_AXIS(axis_right_x);
|
||||
CHECK_RESET_AXIS(axis_right_y);
|
||||
|
||||
#undef CHECK_RESET_AXIS
|
||||
}
|
||||
|
||||
static const char *get_bound_driver(int port)
|
||||
{
|
||||
@@ -171,38 +257,14 @@ void xemu_input_init(void)
|
||||
new_con->peripherals[0] = NULL;
|
||||
new_con->peripherals[1] = NULL;
|
||||
|
||||
sdl_kbd_scancode_map[0] = g_config.input.keyboard_controller_scancode_map.a;
|
||||
sdl_kbd_scancode_map[1] = g_config.input.keyboard_controller_scancode_map.b;
|
||||
sdl_kbd_scancode_map[2] = g_config.input.keyboard_controller_scancode_map.x;
|
||||
sdl_kbd_scancode_map[3] = g_config.input.keyboard_controller_scancode_map.y;
|
||||
sdl_kbd_scancode_map[4] = g_config.input.keyboard_controller_scancode_map.dpad_left;
|
||||
sdl_kbd_scancode_map[5] = g_config.input.keyboard_controller_scancode_map.dpad_up;
|
||||
sdl_kbd_scancode_map[6] = g_config.input.keyboard_controller_scancode_map.dpad_right;
|
||||
sdl_kbd_scancode_map[7] = g_config.input.keyboard_controller_scancode_map.dpad_down;
|
||||
sdl_kbd_scancode_map[8] = g_config.input.keyboard_controller_scancode_map.back;
|
||||
sdl_kbd_scancode_map[9] = g_config.input.keyboard_controller_scancode_map.start;
|
||||
sdl_kbd_scancode_map[10] = g_config.input.keyboard_controller_scancode_map.white;
|
||||
sdl_kbd_scancode_map[11] = g_config.input.keyboard_controller_scancode_map.black;
|
||||
sdl_kbd_scancode_map[12] = g_config.input.keyboard_controller_scancode_map.lstick_btn;
|
||||
sdl_kbd_scancode_map[13] = g_config.input.keyboard_controller_scancode_map.rstick_btn;
|
||||
sdl_kbd_scancode_map[14] = g_config.input.keyboard_controller_scancode_map.guide;
|
||||
sdl_kbd_scancode_map[15] = g_config.input.keyboard_controller_scancode_map.lstick_up;
|
||||
sdl_kbd_scancode_map[16] = g_config.input.keyboard_controller_scancode_map.lstick_left;
|
||||
sdl_kbd_scancode_map[17] = g_config.input.keyboard_controller_scancode_map.lstick_right;
|
||||
sdl_kbd_scancode_map[18] = g_config.input.keyboard_controller_scancode_map.lstick_down;
|
||||
sdl_kbd_scancode_map[19] = g_config.input.keyboard_controller_scancode_map.ltrigger;
|
||||
sdl_kbd_scancode_map[20] = g_config.input.keyboard_controller_scancode_map.rstick_up;
|
||||
sdl_kbd_scancode_map[21] = g_config.input.keyboard_controller_scancode_map.rstick_left;
|
||||
sdl_kbd_scancode_map[22] = g_config.input.keyboard_controller_scancode_map.rstick_right;
|
||||
sdl_kbd_scancode_map[23] = g_config.input.keyboard_controller_scancode_map.rstick_down;
|
||||
sdl_kbd_scancode_map[24] = g_config.input.keyboard_controller_scancode_map.rtrigger;
|
||||
|
||||
for (int i = 0; i < 25; i++) {
|
||||
if( (sdl_kbd_scancode_map[i] < SDL_SCANCODE_UNKNOWN) ||
|
||||
(sdl_kbd_scancode_map[i] >= SDL_NUM_SCANCODES) ) {
|
||||
fprintf(stderr, "WARNING: Keyboard controller map scancode out of range (%d) : Disabled\n", sdl_kbd_scancode_map[i]);
|
||||
sdl_kbd_scancode_map[i] = SDL_SCANCODE_UNKNOWN;
|
||||
}
|
||||
static const char *format_str =
|
||||
"WARNING: Keyboard controller map scancode out of range "
|
||||
"(%d) : Disabled\n";
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), format_str, i);
|
||||
check_and_reset_in_range(g_keyboard_scancode_map[i],
|
||||
SDL_SCANCODE_UNKNOWN, SDL_NUM_SCANCODES, buf);
|
||||
}
|
||||
|
||||
bound_drivers[0] = get_bound_driver(0);
|
||||
@@ -277,7 +339,6 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
|
||||
memset(new_con, 0, sizeof(ControllerState));
|
||||
new_con->type = INPUT_DEVICE_SDL_GAMECONTROLLER;
|
||||
new_con->name = SDL_GameControllerName(sdl_con);
|
||||
new_con->rumble_enabled = true;
|
||||
new_con->sdl_gamecontroller = sdl_con;
|
||||
new_con->sdl_joystick = SDL_GameControllerGetJoystick(new_con->sdl_gamecontroller);
|
||||
new_con->sdl_joystick_id = SDL_JoystickInstanceID(new_con->sdl_joystick);
|
||||
@@ -292,6 +353,8 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
|
||||
SDL_JoystickGetGUIDString(new_con->sdl_joystick_guid, guid_buf, sizeof(guid_buf));
|
||||
DPRINTF("Opened %s (%s)\n", new_con->name, guid_buf);
|
||||
|
||||
xemu_input_bindings_reload_controller_map(new_con, /*reset_to_default=*/false);
|
||||
|
||||
QTAILQ_INSERT_TAIL(&available_controllers, new_con, entry);
|
||||
|
||||
// Do not replace binding for a currently bound device. In the case that
|
||||
@@ -427,21 +490,48 @@ void xemu_input_update_sdl_kbd_controller_state(ControllerState *state)
|
||||
|
||||
const uint8_t *kbd = SDL_GetKeyboardState(NULL);
|
||||
|
||||
for (int i = 0; i < 15; i++) {
|
||||
state->buttons |= kbd[sdl_kbd_scancode_map[i]] << i;
|
||||
}
|
||||
#define KBD_STATE(btn) \
|
||||
(kbd[g_config.input.keyboard_controller_scancode_map.btn])
|
||||
|
||||
if (kbd[sdl_kbd_scancode_map[15]]) state->axis[CONTROLLER_AXIS_LSTICK_Y] = 32767;
|
||||
if (kbd[sdl_kbd_scancode_map[16]]) state->axis[CONTROLLER_AXIS_LSTICK_X] = -32768;
|
||||
if (kbd[sdl_kbd_scancode_map[17]]) state->axis[CONTROLLER_AXIS_LSTICK_X] = 32767;
|
||||
if (kbd[sdl_kbd_scancode_map[18]]) state->axis[CONTROLLER_AXIS_LSTICK_Y] = -32768;
|
||||
if (kbd[sdl_kbd_scancode_map[19]]) state->axis[CONTROLLER_AXIS_LTRIG] = 32767;
|
||||
state->buttons |= KBD_STATE(a) << 0;
|
||||
state->buttons |= KBD_STATE(b) << 1;
|
||||
state->buttons |= KBD_STATE(x) << 2;
|
||||
state->buttons |= KBD_STATE(y) << 3;
|
||||
state->buttons |= KBD_STATE(dpad_left) << 4;
|
||||
state->buttons |= KBD_STATE(dpad_up) << 5;
|
||||
state->buttons |= KBD_STATE(dpad_right) << 6;
|
||||
state->buttons |= KBD_STATE(dpad_down) << 7;
|
||||
state->buttons |= KBD_STATE(back) << 8;
|
||||
state->buttons |= KBD_STATE(start) << 9;
|
||||
state->buttons |= KBD_STATE(white) << 10;
|
||||
state->buttons |= KBD_STATE(black) << 11;
|
||||
state->buttons |= KBD_STATE(lstick_btn) << 12;
|
||||
state->buttons |= KBD_STATE(rstick_btn) << 13;
|
||||
state->buttons |= KBD_STATE(guide) << 14;
|
||||
|
||||
if (kbd[sdl_kbd_scancode_map[20]]) state->axis[CONTROLLER_AXIS_RSTICK_Y] = 32767;
|
||||
if (kbd[sdl_kbd_scancode_map[21]]) state->axis[CONTROLLER_AXIS_RSTICK_X] = -32768;
|
||||
if (kbd[sdl_kbd_scancode_map[22]]) state->axis[CONTROLLER_AXIS_RSTICK_X] = 32767;
|
||||
if (kbd[sdl_kbd_scancode_map[23]]) state->axis[CONTROLLER_AXIS_RSTICK_Y] = -32768;
|
||||
if (kbd[sdl_kbd_scancode_map[24]]) state->axis[CONTROLLER_AXIS_RTRIG] = 32767;
|
||||
if (KBD_STATE(lstick_up))
|
||||
state->axis[CONTROLLER_AXIS_LSTICK_Y] = 32767;
|
||||
if (KBD_STATE(lstick_left))
|
||||
state->axis[CONTROLLER_AXIS_LSTICK_X] = -32768;
|
||||
if (KBD_STATE(lstick_right))
|
||||
state->axis[CONTROLLER_AXIS_LSTICK_X] = 32767;
|
||||
if (KBD_STATE(lstick_down))
|
||||
state->axis[CONTROLLER_AXIS_LSTICK_Y] = -32768;
|
||||
if (KBD_STATE(ltrigger))
|
||||
state->axis[CONTROLLER_AXIS_LTRIG] = 32767;
|
||||
|
||||
if (KBD_STATE(rstick_up))
|
||||
state->axis[CONTROLLER_AXIS_RSTICK_Y] = 32767;
|
||||
if (KBD_STATE(rstick_left))
|
||||
state->axis[CONTROLLER_AXIS_RSTICK_X] = -32768;
|
||||
if (KBD_STATE(rstick_right))
|
||||
state->axis[CONTROLLER_AXIS_RSTICK_X] = 32767;
|
||||
if (KBD_STATE(rstick_down))
|
||||
state->axis[CONTROLLER_AXIS_RSTICK_Y] = -32768;
|
||||
if (KBD_STATE(rtrigger))
|
||||
state->axis[CONTROLLER_AXIS_RTRIG] = 32767;
|
||||
|
||||
#undef KBD_STATE
|
||||
}
|
||||
|
||||
void xemu_input_update_sdl_controller_state(ControllerState *state)
|
||||
@@ -449,48 +539,76 @@ void xemu_input_update_sdl_controller_state(ControllerState *state)
|
||||
state->buttons = 0;
|
||||
memset(state->axis, 0, sizeof(state->axis));
|
||||
|
||||
const SDL_GameControllerButton sdl_button_map[15] = {
|
||||
SDL_CONTROLLER_BUTTON_A,
|
||||
SDL_CONTROLLER_BUTTON_B,
|
||||
SDL_CONTROLLER_BUTTON_X,
|
||||
SDL_CONTROLLER_BUTTON_Y,
|
||||
SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
||||
SDL_CONTROLLER_BUTTON_DPAD_UP,
|
||||
SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
|
||||
SDL_CONTROLLER_BUTTON_DPAD_DOWN,
|
||||
SDL_CONTROLLER_BUTTON_BACK,
|
||||
SDL_CONTROLLER_BUTTON_START,
|
||||
SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
|
||||
SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
|
||||
SDL_CONTROLLER_BUTTON_LEFTSTICK,
|
||||
SDL_CONTROLLER_BUTTON_RIGHTSTICK,
|
||||
SDL_CONTROLLER_BUTTON_GUIDE
|
||||
};
|
||||
#define SDL_MASK_BUTTON(state, btn, idx) \
|
||||
(SDL_GameControllerGetButton( \
|
||||
(state)->sdl_gamecontroller, \
|
||||
(state)->controller_map->controller_mapping.btn) \
|
||||
<< idx)
|
||||
|
||||
for (int i = 0; i < 15; i++) {
|
||||
state->buttons |= SDL_GameControllerGetButton(state->sdl_gamecontroller, sdl_button_map[i]) << i;
|
||||
state->buttons |= SDL_MASK_BUTTON(state, a, 0);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, b, 1);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, x, 2);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, y, 3);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, dpad_left, 4);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, dpad_up, 5);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, dpad_right, 6);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, dpad_down, 7);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, back, 8);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, start, 9);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, lshoulder, 10);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, rshoulder, 11);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, lstick_btn, 12);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, rstick_btn, 13);
|
||||
state->buttons |= SDL_MASK_BUTTON(state, guide, 14);
|
||||
|
||||
#undef SDL_MASK_BUTTON
|
||||
|
||||
#define SDL_GET_AXIS(state, axis) \
|
||||
SDL_GameControllerGetAxis( \
|
||||
(state)->sdl_gamecontroller, \
|
||||
(state)->controller_map->controller_mapping.axis)
|
||||
|
||||
state->axis[0] = SDL_GET_AXIS(state, axis_trigger_left);
|
||||
state->axis[1] = SDL_GET_AXIS(state, axis_trigger_right);
|
||||
state->axis[2] = SDL_GET_AXIS(state, axis_left_x);
|
||||
state->axis[3] = SDL_GET_AXIS(state, axis_left_y);
|
||||
state->axis[4] = SDL_GET_AXIS(state, axis_right_x);
|
||||
state->axis[5] = SDL_GET_AXIS(state, axis_right_y);
|
||||
|
||||
#undef SDL_GET_AXIS
|
||||
|
||||
// FIXME: Check range
|
||||
#define INVERT_AXIS(controller_axis) \
|
||||
state->axis[controller_axis] = -1 - state->axis[controller_axis]
|
||||
|
||||
if (state->controller_map->controller_mapping.invert_axis_left_x) {
|
||||
INVERT_AXIS(CONTROLLER_AXIS_LSTICK_X);
|
||||
}
|
||||
|
||||
const SDL_GameControllerAxis sdl_axis_map[6] = {
|
||||
SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
||||
SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY,
|
||||
SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY,
|
||||
};
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
state->axis[i] = SDL_GameControllerGetAxis(state->sdl_gamecontroller, sdl_axis_map[i]);
|
||||
if (!state->controller_map->controller_mapping.invert_axis_left_y) {
|
||||
INVERT_AXIS(CONTROLLER_AXIS_LSTICK_Y);
|
||||
}
|
||||
|
||||
// FIXME: Check range
|
||||
state->axis[CONTROLLER_AXIS_LSTICK_Y] = -1 - state->axis[CONTROLLER_AXIS_LSTICK_Y];
|
||||
state->axis[CONTROLLER_AXIS_RSTICK_Y] = -1 - state->axis[CONTROLLER_AXIS_RSTICK_Y];
|
||||
if (state->controller_map->controller_mapping.invert_axis_right_x) {
|
||||
INVERT_AXIS(CONTROLLER_AXIS_RSTICK_X);
|
||||
}
|
||||
|
||||
if (!state->controller_map->controller_mapping.invert_axis_right_y) {
|
||||
INVERT_AXIS(CONTROLLER_AXIS_RSTICK_Y);
|
||||
}
|
||||
|
||||
#undef INVERT_AXIS
|
||||
|
||||
// xemu_input_print_controller_state(state);
|
||||
}
|
||||
|
||||
void xemu_input_update_rumble(ControllerState *state)
|
||||
{
|
||||
if (!state->rumble_enabled || !g_config.input.allow_vibration) {
|
||||
if (state->type != INPUT_DEVICE_SDL_GAMECONTROLLER) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state->controller_map->enable_rumble) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -794,3 +912,12 @@ int xemu_input_get_test_mode(void)
|
||||
{
|
||||
return test_mode;
|
||||
}
|
||||
|
||||
void xemu_input_reset_input_mapping(ControllerState *state)
|
||||
{
|
||||
if (state->type == INPUT_DEVICE_SDL_GAMECONTROLLER) {
|
||||
xemu_input_bindings_reload_controller_map(state, /*reset_to_default=*/true);
|
||||
} else if (state->type == INPUT_DEVICE_SDL_KEYBOARD) {
|
||||
xemu_settings_reset_keyboard_mapping();
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -29,6 +29,8 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "qemu/queue.h"
|
||||
#include "xemu-settings.h"
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#define DRIVER_DUKE "usb-xbox-gamepad"
|
||||
#define DRIVER_S "usb-xbox-gamepad-s"
|
||||
@@ -66,6 +68,12 @@ enum controller_state_axis_index {
|
||||
CONTROLLER_AXIS__COUNT,
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
using GamepadMappings = struct config::input::gamepad_mappings;
|
||||
#else
|
||||
typedef struct gamepad_mappings GamepadMappings;
|
||||
#endif
|
||||
|
||||
enum controller_input_device_type {
|
||||
INPUT_DEVICE_SDL_KEYBOARD,
|
||||
INPUT_DEVICE_SDL_GAMECONTROLLER,
|
||||
@@ -93,7 +101,6 @@ typedef struct ControllerState {
|
||||
uint32_t animate_trigger_end;
|
||||
|
||||
// Rumble state
|
||||
bool rumble_enabled;
|
||||
uint16_t rumble_l, rumble_r;
|
||||
|
||||
enum controller_input_device_type type;
|
||||
@@ -106,6 +113,8 @@ typedef struct ControllerState {
|
||||
enum peripheral_type peripheral_types[2];
|
||||
void *peripherals[2];
|
||||
|
||||
GamepadMappings *controller_map;
|
||||
|
||||
int bound; // Which port this input device is bound to
|
||||
void *device; // DeviceState opaque
|
||||
} ControllerState;
|
||||
@@ -139,6 +148,7 @@ void xemu_save_peripheral_settings(int player_index, int peripheral_index,
|
||||
|
||||
void xemu_input_set_test_mode(int enabled);
|
||||
int xemu_input_get_test_mode(void);
|
||||
void xemu_input_reset_input_mapping(ControllerState *state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <iostream>
|
||||
#include <locale.h>
|
||||
|
||||
#include "xemu-controllers.h"
|
||||
#include "xemu-settings.h"
|
||||
|
||||
#define DEFINE_CONFIG_TREE
|
||||
@@ -217,6 +218,11 @@ void xemu_settings_save(void)
|
||||
/* Ensure numeric values are printed with '.' radix, no grouping */
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
|
||||
// The global controller vibration setting is replaced with a per-controller config.
|
||||
// xemu_settings_load_gamepad_mapping should have migrated that setting to any connected
|
||||
// controller, so we can set it to true (default) now to remove it from the user config.
|
||||
g_config.input.allow_vibration = true;
|
||||
|
||||
config_tree.update_from_struct(&g_config);
|
||||
fprintf(fd, "%s", config_tree.generate_delta_toml().c_str());
|
||||
fclose(fd);
|
||||
@@ -254,3 +260,57 @@ void remove_net_nat_forward_ports(unsigned int index)
|
||||
cnode->free_allocations(&g_config);
|
||||
cnode->store_to_struct(&g_config);
|
||||
}
|
||||
|
||||
GamepadMappings *xemu_settings_load_gamepad_mapping(const char *guid, bool reset_to_default)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned int gamepad_mappings_count = g_config.input.gamepad_mappings_count;
|
||||
for (i = 0; i < gamepad_mappings_count; ++i) {
|
||||
auto *mapping = &g_config.input.gamepad_mappings[i];
|
||||
if (strcmp(mapping->gamepad_id, guid) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (reset_to_default) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Migrate global 'allow_vibration' setting to the controller config
|
||||
mapping->enable_rumble = g_config.input.allow_vibration;
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
auto cnode = config_tree.child("input")->child("gamepad_mappings");
|
||||
cnode->update_from_struct(&g_config);
|
||||
cnode->free_allocations(&g_config);
|
||||
|
||||
CNode *mapping_node;
|
||||
if (reset_to_default && i < gamepad_mappings_count) {
|
||||
mapping_node = &cnode->children[i];
|
||||
mapping_node->reset_to_defaults();
|
||||
} else {
|
||||
cnode->children.push_back(*cnode->array_item_type);
|
||||
mapping_node = &cnode->children.back();
|
||||
}
|
||||
|
||||
mapping_node->child("gamepad_id")->set_string(guid);
|
||||
|
||||
cnode->store_to_struct(&g_config);
|
||||
|
||||
auto *mapping = &g_config.input
|
||||
.gamepad_mappings[g_config.input.gamepad_mappings_count - 1];
|
||||
|
||||
// Migrate global 'allow_vibration' setting to the controller config
|
||||
mapping->enable_rumble = g_config.input.allow_vibration;
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
void xemu_settings_reset_keyboard_mapping(void)
|
||||
{
|
||||
auto cnode = config_tree.child("input")->child("keyboard_controller_scancode_map");
|
||||
cnode->update_from_struct(&g_config);
|
||||
cnode->reset_to_defaults();
|
||||
cnode->store_to_struct(&g_config);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ static inline void xemu_settings_set_string(const char **str, const char *new_st
|
||||
void add_net_nat_forward_ports(int host, int guest, CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol);
|
||||
void remove_net_nat_forward_ports(unsigned int index);
|
||||
|
||||
// Reset keyboard mappings to default settings.
|
||||
void xemu_settings_reset_keyboard_mapping(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -558,8 +558,10 @@ void sdl2_poll_events(struct sdl2_console *scon)
|
||||
xemu_hud_should_capture_kbd_mouse(&kbd, &mouse);
|
||||
|
||||
while (SDL_PollEvent(ev)) {
|
||||
xemu_input_process_sdl_events(ev);
|
||||
// HUD must process events first so that if a controller is detached,
|
||||
// a latent rebind request can cancel before the state is freed
|
||||
xemu_hud_process_sdl_events(ev);
|
||||
xemu_input_process_sdl_events(ev);
|
||||
|
||||
switch (ev->type) {
|
||||
case SDL_KEYDOWN:
|
||||
|
||||
+14
-9
@@ -1,6 +1,7 @@
|
||||
#include "common.hh"
|
||||
#include "ui/xui/main-menu.hh"
|
||||
#include "input-manager.hh"
|
||||
#include "../xemu-input.h"
|
||||
#include "common.hh"
|
||||
|
||||
InputManager g_input_mgr;
|
||||
|
||||
@@ -18,14 +19,18 @@ void InputManager::Update()
|
||||
m_buttons = 0;
|
||||
int16_t axis[CONTROLLER_AXIS__COUNT] = {0};
|
||||
|
||||
ControllerState *iter;
|
||||
QTAILQ_FOREACH(iter, &available_controllers, entry) {
|
||||
if (iter->type != INPUT_DEVICE_SDL_GAMECONTROLLER) continue;
|
||||
m_buttons |= iter->buttons;
|
||||
// We simply take any axis that is >10 % activation
|
||||
for (int i = 0; i < CONTROLLER_AXIS__COUNT; i++) {
|
||||
if ((iter->axis[i] > 3276) || (iter->axis[i] < -3276)) {
|
||||
axis[i] = iter->axis[i];
|
||||
// If we are rebinding a controller, prevent navigation
|
||||
if (!g_main_menu.IsInputRebinding()) {
|
||||
ControllerState *iter;
|
||||
QTAILQ_FOREACH (iter, &available_controllers, entry) {
|
||||
if (iter->type != INPUT_DEVICE_SDL_GAMECONTROLLER)
|
||||
continue;
|
||||
m_buttons |= iter->buttons;
|
||||
// We simply take any axis that is >10 % activation
|
||||
for (int i = 0; i < CONTROLLER_AXIS__COUNT; i++) {
|
||||
if ((iter->axis[i] > 3276) || (iter->axis[i] < -3276)) {
|
||||
axis[i] = iter->axis[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+245
-8
@@ -47,7 +47,9 @@
|
||||
MainMenuScene g_main_menu;
|
||||
|
||||
MainMenuTabView::~MainMenuTabView() {}
|
||||
void MainMenuTabView::Draw() {}
|
||||
void MainMenuTabView::Draw()
|
||||
{
|
||||
}
|
||||
|
||||
void MainMenuGeneralView::Draw()
|
||||
{
|
||||
@@ -76,6 +78,25 @@ void MainMenuGeneralView::Draw()
|
||||
// "Limit DVD/HDD throughput to approximate Xbox load times");
|
||||
}
|
||||
|
||||
bool MainMenuInputView::ConsumeRebindEvent(SDL_Event *event)
|
||||
{
|
||||
if (!m_rebinding) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RebindEventResult rebind_result = m_rebinding->ConsumeRebindEvent(event);
|
||||
if (rebind_result == RebindEventResult::Complete) {
|
||||
m_rebinding = nullptr;
|
||||
}
|
||||
|
||||
return rebind_result == RebindEventResult::Ignore;
|
||||
}
|
||||
|
||||
bool MainMenuInputView::IsInputRebinding()
|
||||
{
|
||||
return m_rebinding != nullptr;
|
||||
}
|
||||
|
||||
void MainMenuInputView::Draw()
|
||||
{
|
||||
SectionTitle("Controllers");
|
||||
@@ -144,6 +165,7 @@ void MainMenuInputView::Draw()
|
||||
|
||||
if (activated) {
|
||||
active = i;
|
||||
m_rebinding = nullptr;
|
||||
}
|
||||
|
||||
uint32_t port_color = 0xafafafff;
|
||||
@@ -185,10 +207,8 @@ void MainMenuInputView::Draw()
|
||||
if (ImGui::BeginCombo("###InputDrivers", driver,
|
||||
ImGuiComboFlags_NoArrowButton)) {
|
||||
const char *available_drivers[] = { DRIVER_DUKE, DRIVER_S };
|
||||
const char *driver_display_names[] = {
|
||||
DRIVER_DUKE_DISPLAY_NAME,
|
||||
DRIVER_S_DISPLAY_NAME
|
||||
};
|
||||
const char *driver_display_names[] = { DRIVER_DUKE_DISPLAY_NAME,
|
||||
DRIVER_S_DISPLAY_NAME };
|
||||
bool is_selected = false;
|
||||
int num_drivers = sizeof(driver_display_names) / sizeof(driver_display_names[0]);
|
||||
for (int i = 0; i < num_drivers; i++) {
|
||||
@@ -298,7 +318,7 @@ void MainMenuInputView::Draw()
|
||||
device_selected = true;
|
||||
RenderController(0, 0, 0x81dc8a00, 0x0f0f0f00, bound_state);
|
||||
} else {
|
||||
static ControllerState state = { 0 };
|
||||
static ControllerState state{};
|
||||
RenderController(0, 0, 0x1f1f1f00, 0x0f0f0f00, &state);
|
||||
}
|
||||
|
||||
@@ -496,16 +516,221 @@ void MainMenuInputView::Draw()
|
||||
ImGui::Columns(1);
|
||||
}
|
||||
|
||||
SectionTitle("Mapping");
|
||||
ImVec4 tc = ImGui::GetStyle().Colors[ImGuiCol_Header];
|
||||
tc.w = 0.0f;
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, tc);
|
||||
|
||||
if (ImGui::CollapsingHeader("Input Mapping")) {
|
||||
float p = ImGui::GetFrameHeight() * 0.3;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(p, p));
|
||||
if (ImGui::BeginTable("input_remap_tbl", 2,
|
||||
ImGuiTableFlags_RowBg |
|
||||
ImGuiTableFlags_Borders)) {
|
||||
ImGui::TableSetupColumn("Emulated Input");
|
||||
ImGui::TableSetupColumn("Host Input");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
PopulateTableController(bound_state);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
if (bound_state && bound_state->type == INPUT_DEVICE_SDL_GAMECONTROLLER) {
|
||||
Toggle("Enable Rumble", &bound_state->controller_map->enable_rumble);
|
||||
Toggle("Invert Left X Axis",
|
||||
&bound_state->controller_map->controller_mapping
|
||||
.invert_axis_left_x);
|
||||
Toggle("Invert Left Y Axis",
|
||||
&bound_state->controller_map->controller_mapping
|
||||
.invert_axis_left_y);
|
||||
Toggle("Invert Right X Axis",
|
||||
&bound_state->controller_map->controller_mapping
|
||||
.invert_axis_right_x);
|
||||
Toggle("Invert Right Y Axis",
|
||||
&bound_state->controller_map->controller_mapping
|
||||
.invert_axis_right_y);
|
||||
}
|
||||
|
||||
if (ImGui::Button("Reset to Default")) {
|
||||
xemu_input_reset_input_mapping(bound_state);
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
SectionTitle("Options");
|
||||
Toggle("Auto-bind controllers", &g_config.input.auto_bind,
|
||||
"Bind newly connected controllers to any open port");
|
||||
Toggle("Controller vibration", &g_config.input.allow_vibration,
|
||||
"Allows the controllers to vibrate");
|
||||
Toggle("Background controller input capture",
|
||||
&g_config.input.background_input_capture,
|
||||
"Capture even if window is unfocused (requires restart)");
|
||||
}
|
||||
|
||||
void MainMenuInputView::Hide()
|
||||
{
|
||||
m_rebinding = nullptr;
|
||||
}
|
||||
|
||||
void MainMenuInputView::PopulateTableController(ControllerState *state)
|
||||
{
|
||||
if (!state)
|
||||
return;
|
||||
|
||||
// Must match g_keyboard_scancode_map and the controller
|
||||
// button map below.
|
||||
static constexpr const char *face_button_index_to_name_map[15] = {
|
||||
"A",
|
||||
"B",
|
||||
"X",
|
||||
"Y",
|
||||
"Back",
|
||||
"Guide",
|
||||
"Start",
|
||||
"Left Stick Button",
|
||||
"Right Stick Button",
|
||||
"White",
|
||||
"Black",
|
||||
"DPad Up",
|
||||
"DPad Down",
|
||||
"DPad Left",
|
||||
"DPad Right",
|
||||
};
|
||||
|
||||
// Must match g_keyboard_scancode_map[15:]. Each axis requires
|
||||
// two keys for the positive and negative direction with the
|
||||
// exception of the triggers, which only require one each.
|
||||
static constexpr const char *keyboard_stick_index_to_name_map[10] = {
|
||||
"Left Stick Up",
|
||||
"Left Stick Left",
|
||||
"Left Stick Right",
|
||||
"Left Stick Down",
|
||||
"Left Trigger",
|
||||
"Right Stick Up",
|
||||
"Right Stick Left",
|
||||
"Right Stick Right",
|
||||
"Right Stick Down",
|
||||
"Right Trigger",
|
||||
};
|
||||
|
||||
// Must match controller axis map below.
|
||||
static constexpr const char *gamepad_axis_index_to_name_map[6] = {
|
||||
"Left Stick Axis X",
|
||||
"Left Stick Axis Y",
|
||||
"Right Stick Axis X",
|
||||
"Right Stick Axis Y",
|
||||
"Left Trigger Axis",
|
||||
"Right Trigger Axis",
|
||||
};
|
||||
|
||||
bool is_keyboard = state->type == INPUT_DEVICE_SDL_KEYBOARD;
|
||||
|
||||
int num_axis_mappings;
|
||||
const char *const *axis_index_to_name_map;
|
||||
if (is_keyboard) {
|
||||
num_axis_mappings = std::size(keyboard_stick_index_to_name_map);
|
||||
axis_index_to_name_map = keyboard_stick_index_to_name_map;
|
||||
} else {
|
||||
num_axis_mappings = std::size(gamepad_axis_index_to_name_map);
|
||||
axis_index_to_name_map = gamepad_axis_index_to_name_map;
|
||||
}
|
||||
|
||||
constexpr int num_face_buttons = std::size(face_button_index_to_name_map);
|
||||
const int table_rows = num_axis_mappings + num_face_buttons;
|
||||
for (int i = 0; i < table_rows; ++i) {
|
||||
ImGui::TableNextRow();
|
||||
|
||||
// Button/Axis Name Column
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
|
||||
if (i < num_face_buttons) {
|
||||
ImGui::Text("%s", face_button_index_to_name_map[i]);
|
||||
} else {
|
||||
ImGui::Text("%s", axis_index_to_name_map[i - num_face_buttons]);
|
||||
}
|
||||
|
||||
// Button Binding Column
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
||||
if (m_rebinding && m_rebinding->GetTableRow() == i) {
|
||||
ImGui::Text("Press a key to rebind");
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *remap_button_text = "Invalid";
|
||||
if (is_keyboard) {
|
||||
// g_keyboard_scancode_map includes both face buttons and axis buttons.
|
||||
int keycode = *(g_keyboard_scancode_map[i]);
|
||||
if (keycode != SDL_SCANCODE_UNKNOWN) {
|
||||
remap_button_text =
|
||||
SDL_GetScancodeName(static_cast<SDL_Scancode>(keycode));
|
||||
}
|
||||
} else if (i < num_face_buttons) {
|
||||
int *button_map[num_face_buttons] = {
|
||||
&state->controller_map->controller_mapping.a,
|
||||
&state->controller_map->controller_mapping.b,
|
||||
&state->controller_map->controller_mapping.x,
|
||||
&state->controller_map->controller_mapping.y,
|
||||
&state->controller_map->controller_mapping.back,
|
||||
&state->controller_map->controller_mapping.guide,
|
||||
&state->controller_map->controller_mapping.start,
|
||||
&state->controller_map->controller_mapping.lstick_btn,
|
||||
&state->controller_map->controller_mapping.rstick_btn,
|
||||
&state->controller_map->controller_mapping.lshoulder,
|
||||
&state->controller_map->controller_mapping.rshoulder,
|
||||
&state->controller_map->controller_mapping.dpad_up,
|
||||
&state->controller_map->controller_mapping.dpad_down,
|
||||
&state->controller_map->controller_mapping.dpad_left,
|
||||
&state->controller_map->controller_mapping.dpad_right,
|
||||
};
|
||||
|
||||
int button = *(button_map[i]);
|
||||
if (button != SDL_CONTROLLER_BUTTON_INVALID) {
|
||||
remap_button_text = SDL_GameControllerGetStringForButton(
|
||||
static_cast<SDL_GameControllerButton>(button));
|
||||
}
|
||||
} else {
|
||||
int *axis_map[6] = {
|
||||
&state->controller_map->controller_mapping.axis_left_x,
|
||||
&state->controller_map->controller_mapping.axis_left_y,
|
||||
&state->controller_map->controller_mapping.axis_right_x,
|
||||
&state->controller_map->controller_mapping.axis_right_y,
|
||||
&state->controller_map->controller_mapping
|
||||
.axis_trigger_left,
|
||||
&state->controller_map->controller_mapping
|
||||
.axis_trigger_right,
|
||||
};
|
||||
int axis = *(axis_map[i - num_face_buttons]);
|
||||
if (axis != SDL_CONTROLLER_AXIS_INVALID) {
|
||||
remap_button_text = SDL_GameControllerGetStringForAxis(
|
||||
static_cast<SDL_GameControllerAxis>(axis));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PushID(i);
|
||||
float tw = ImGui::CalcTextSize(remap_button_text).x;
|
||||
auto &style = ImGui::GetStyle();
|
||||
float max_button_width =
|
||||
tw + g_viewport_mgr.m_scale * 2 * style.FramePadding.x;
|
||||
|
||||
float min_button_width = ImGui::GetColumnWidth(1) / 2;
|
||||
float button_width = std::max(min_button_width, max_button_width);
|
||||
|
||||
if (ImGui::Button(remap_button_text, ImVec2(button_width, 0))) {
|
||||
if (is_keyboard) {
|
||||
m_rebinding =
|
||||
std::make_unique<ControllerKeyboardRebindingMap>(i);
|
||||
} else {
|
||||
m_rebinding =
|
||||
std::make_unique<ControllerGamepadRebindingMap>(i,
|
||||
state);
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
void MainMenuDisplayView::Draw()
|
||||
{
|
||||
SectionTitle("Renderer");
|
||||
@@ -1532,6 +1757,7 @@ void MainMenuScene::Show()
|
||||
|
||||
void MainMenuScene::Hide()
|
||||
{
|
||||
m_views[m_current_view_index]->Hide();
|
||||
m_background.Hide();
|
||||
m_nav_control_view.Hide();
|
||||
m_animation.EaseOut();
|
||||
@@ -1544,6 +1770,7 @@ bool MainMenuScene::IsAnimating()
|
||||
|
||||
void MainMenuScene::SetNextViewIndex(int i)
|
||||
{
|
||||
m_views[m_current_view_index]->Hide();
|
||||
m_next_view_index = i % m_tabs.size();
|
||||
g_config.general.last_viewed_menu_index = i;
|
||||
}
|
||||
@@ -1583,6 +1810,16 @@ void MainMenuScene::UpdateAboutViewConfigInfo()
|
||||
m_about_view.UpdateConfigInfoText();
|
||||
}
|
||||
|
||||
bool MainMenuScene::ConsumeRebindEvent(SDL_Event *event)
|
||||
{
|
||||
return m_input_view.ConsumeRebindEvent(event);
|
||||
}
|
||||
|
||||
bool MainMenuScene::IsInputRebinding()
|
||||
{
|
||||
return m_input_view.IsInputRebinding();
|
||||
}
|
||||
|
||||
bool MainMenuScene::Draw()
|
||||
{
|
||||
m_animation.Step();
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "scene.hh"
|
||||
#include "scene-components.hh"
|
||||
#include "../xemu-snapshots.h"
|
||||
#include "../xemu-controllers.h"
|
||||
#include "ui/xemu-input.h"
|
||||
|
||||
extern "C" {
|
||||
#include "net/pcap.h"
|
||||
@@ -36,6 +38,9 @@ class MainMenuTabView
|
||||
public:
|
||||
virtual ~MainMenuTabView();
|
||||
virtual void Draw();
|
||||
virtual void Hide()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class MainMenuGeneralView : public virtual MainMenuTabView
|
||||
@@ -47,7 +52,16 @@ public:
|
||||
class MainMenuInputView : public virtual MainMenuTabView
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<RebindingMap> m_rebinding;
|
||||
|
||||
MainMenuInputView() : m_rebinding{ nullptr }
|
||||
{
|
||||
}
|
||||
bool ConsumeRebindEvent(SDL_Event *event);
|
||||
bool IsInputRebinding();
|
||||
void Draw() override;
|
||||
void Hide() override;
|
||||
void PopulateTableController(ControllerState *state);
|
||||
};
|
||||
|
||||
class MainMenuDisplayView : public virtual MainMenuTabView
|
||||
@@ -193,6 +207,8 @@ public:
|
||||
void SetNextViewIndex(int i);
|
||||
void HandleInput();
|
||||
void UpdateAboutViewConfigInfo();
|
||||
bool ConsumeRebindEvent(SDL_Event *event);
|
||||
bool IsInputRebinding();
|
||||
bool Draw() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -170,6 +170,11 @@ void xemu_hud_cleanup(void)
|
||||
|
||||
void xemu_hud_process_sdl_events(SDL_Event *event)
|
||||
{
|
||||
// Ignore inputs that are consumed by rebinding
|
||||
if (g_main_menu.ConsumeRebindEvent(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui_ImplSDL2_ProcessEvent(event);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user